paperclip 3.4.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (220) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +17 -0
  3. data/.github/issue_template.md +3 -0
  4. data/.gitignore +0 -6
  5. data/.hound.yml +1055 -0
  6. data/.rubocop.yml +1 -0
  7. data/.travis.yml +19 -12
  8. data/Appraisals +4 -11
  9. data/CONTRIBUTING.md +29 -13
  10. data/Gemfile +13 -4
  11. data/LICENSE +1 -3
  12. data/MIGRATING-ES.md +317 -0
  13. data/MIGRATING.md +375 -0
  14. data/NEWS +390 -71
  15. data/README.md +607 -152
  16. data/RELEASING.md +17 -0
  17. data/Rakefile +6 -8
  18. data/UPGRADING +12 -9
  19. data/features/basic_integration.feature +34 -21
  20. data/features/migration.feature +0 -24
  21. data/features/rake_tasks.feature +2 -3
  22. data/features/step_definitions/attachment_steps.rb +44 -36
  23. data/features/step_definitions/html_steps.rb +2 -2
  24. data/features/step_definitions/rails_steps.rb +125 -26
  25. data/features/step_definitions/s3_steps.rb +3 -3
  26. data/features/step_definitions/web_steps.rb +1 -103
  27. data/features/support/env.rb +3 -2
  28. data/features/support/fakeweb.rb +4 -1
  29. data/features/support/file_helpers.rb +12 -2
  30. data/features/support/fixtures/gemfile.txt +1 -1
  31. data/features/support/paths.rb +1 -1
  32. data/features/support/rails.rb +4 -11
  33. data/gemfiles/4.2.gemfile +17 -0
  34. data/gemfiles/5.0.gemfile +17 -0
  35. data/lib/generators/paperclip/paperclip_generator.rb +9 -3
  36. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +2 -2
  37. data/lib/paperclip/attachment.rb +215 -82
  38. data/lib/paperclip/attachment_registry.rb +60 -0
  39. data/lib/paperclip/callbacks.rb +13 -1
  40. data/lib/paperclip/content_type_detector.rb +48 -24
  41. data/lib/paperclip/errors.rb +8 -1
  42. data/lib/paperclip/file_command_content_type_detector.rb +6 -8
  43. data/lib/paperclip/filename_cleaner.rb +15 -0
  44. data/lib/paperclip/geometry_detector_factory.rb +12 -5
  45. data/lib/paperclip/geometry_parser_factory.rb +1 -1
  46. data/lib/paperclip/glue.rb +1 -2
  47. data/lib/paperclip/has_attached_file.rb +115 -0
  48. data/lib/paperclip/helpers.rb +15 -20
  49. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  50. data/lib/paperclip/interpolations.rb +36 -14
  51. data/lib/paperclip/io_adapters/abstract_adapter.rb +42 -5
  52. data/lib/paperclip/io_adapters/attachment_adapter.rb +20 -9
  53. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  54. data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
  55. data/lib/paperclip/io_adapters/file_adapter.rb +13 -7
  56. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
  57. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -6
  58. data/lib/paperclip/io_adapters/nil_adapter.rb +8 -5
  59. data/lib/paperclip/io_adapters/registry.rb +6 -2
  60. data/lib/paperclip/io_adapters/stringio_adapter.rb +15 -16
  61. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +11 -7
  62. data/lib/paperclip/io_adapters/uri_adapter.rb +43 -19
  63. data/lib/paperclip/locales/en.yml +1 -0
  64. data/lib/paperclip/logger.rb +1 -1
  65. data/lib/paperclip/matchers/have_attached_file_matcher.rb +3 -6
  66. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +4 -4
  67. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +7 -2
  68. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +2 -1
  69. data/lib/paperclip/matchers.rb +1 -1
  70. data/lib/paperclip/media_type_spoof_detector.rb +93 -0
  71. data/lib/paperclip/missing_attachment_styles.rb +11 -16
  72. data/lib/paperclip/processor.rb +15 -43
  73. data/lib/paperclip/processor_helpers.rb +50 -0
  74. data/lib/paperclip/rails_environment.rb +25 -0
  75. data/lib/paperclip/schema.rb +10 -8
  76. data/lib/paperclip/storage/filesystem.rb +20 -5
  77. data/lib/paperclip/storage/fog.rb +49 -23
  78. data/lib/paperclip/storage/s3.rb +153 -82
  79. data/lib/paperclip/style.rb +8 -3
  80. data/lib/paperclip/tempfile_factory.rb +6 -4
  81. data/lib/paperclip/thumbnail.rb +35 -19
  82. data/lib/paperclip/url_generator.rb +26 -14
  83. data/lib/paperclip/validators/attachment_content_type_validator.rb +15 -2
  84. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  85. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  86. data/lib/paperclip/validators/attachment_presence_validator.rb +12 -8
  87. data/lib/paperclip/validators/attachment_size_validator.rb +17 -10
  88. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +31 -0
  89. data/lib/paperclip/validators.rb +31 -3
  90. data/lib/paperclip/version.rb +3 -1
  91. data/lib/paperclip.rb +41 -55
  92. data/lib/tasks/paperclip.rake +56 -9
  93. data/paperclip.gemspec +18 -17
  94. data/shoulda_macros/paperclip.rb +13 -3
  95. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  96. data/spec/paperclip/attachment_processing_spec.rb +79 -0
  97. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  98. data/{test/attachment_test.rb → spec/paperclip/attachment_spec.rb} +597 -389
  99. data/spec/paperclip/content_type_detector_spec.rb +48 -0
  100. data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
  101. data/spec/paperclip/filename_cleaner_spec.rb +13 -0
  102. data/spec/paperclip/geometry_detector_spec.rb +39 -0
  103. data/{test/geometry_parser_test.rb → spec/paperclip/geometry_parser_spec.rb} +27 -27
  104. data/{test/geometry_test.rb → spec/paperclip/geometry_spec.rb} +50 -52
  105. data/spec/paperclip/glue_spec.rb +44 -0
  106. data/spec/paperclip/has_attached_file_spec.rb +158 -0
  107. data/{test/integration_test.rb → spec/paperclip/integration_spec.rb} +179 -199
  108. data/{test/interpolations_test.rb → spec/paperclip/interpolations_spec.rb} +79 -46
  109. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
  110. data/{test/io_adapters/attachment_adapter_test.rb → spec/paperclip/io_adapters/attachment_adapter_spec.rb} +54 -25
  111. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +89 -0
  112. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  113. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  114. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +138 -0
  115. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  116. data/{test/io_adapters/nil_adapter_test.rb → spec/paperclip/io_adapters/nil_adapter_spec.rb} +7 -7
  117. data/{test/io_adapters/registry_test.rb → spec/paperclip/io_adapters/registry_spec.rb} +12 -9
  118. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  119. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  120. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +220 -0
  121. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  122. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +109 -0
  123. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  124. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  125. data/spec/paperclip/media_type_spoof_detector_spec.rb +120 -0
  126. data/spec/paperclip/meta_class_spec.rb +30 -0
  127. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  128. data/spec/paperclip/paperclip_spec.rb +192 -0
  129. data/spec/paperclip/plural_cache_spec.rb +37 -0
  130. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  131. data/{test/processor_test.rb → spec/paperclip/processor_spec.rb} +7 -7
  132. data/spec/paperclip/rails_environment_spec.rb +33 -0
  133. data/spec/paperclip/rake_spec.rb +103 -0
  134. data/spec/paperclip/schema_spec.rb +248 -0
  135. data/{test/storage/filesystem_test.rb → spec/paperclip/storage/filesystem_spec.rb} +18 -18
  136. data/spec/paperclip/storage/fog_spec.rb +566 -0
  137. data/spec/paperclip/storage/s3_live_spec.rb +188 -0
  138. data/spec/paperclip/storage/s3_spec.rb +1693 -0
  139. data/spec/paperclip/style_spec.rb +254 -0
  140. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  141. data/spec/paperclip/tempfile_spec.rb +35 -0
  142. data/{test/thumbnail_test.rb → spec/paperclip/thumbnail_spec.rb} +186 -141
  143. data/spec/paperclip/url_generator_spec.rb +221 -0
  144. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  145. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  146. data/{test/validators/attachment_presence_validator_test.rb → spec/paperclip/validators/attachment_presence_validator_spec.rb} +20 -20
  147. data/{test/validators/attachment_size_validator_test.rb → spec/paperclip/validators/attachment_size_validator_spec.rb} +87 -59
  148. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  149. data/spec/paperclip/validators_spec.rb +164 -0
  150. data/spec/spec_helper.rb +46 -0
  151. data/spec/support/assertions.rb +82 -0
  152. data/spec/support/fake_model.rb +25 -0
  153. data/spec/support/fake_rails.rb +12 -0
  154. data/spec/support/fixtures/empty.html +1 -0
  155. data/spec/support/fixtures/empty.xlsx +0 -0
  156. data/spec/support/fixtures/spaced file.jpg +0 -0
  157. data/spec/support/matchers/accept.rb +5 -0
  158. data/spec/support/matchers/exist.rb +5 -0
  159. data/spec/support/matchers/have_column.rb +23 -0
  160. data/{test → spec}/support/mock_attachment.rb +2 -0
  161. data/{test → spec}/support/mock_url_generator_builder.rb +2 -2
  162. data/spec/support/model_reconstruction.rb +68 -0
  163. data/spec/support/reporting.rb +11 -0
  164. data/spec/support/test_data.rb +13 -0
  165. data/spec/support/version_helper.rb +9 -0
  166. metadata +395 -346
  167. data/Gemfile.lock +0 -200
  168. data/RUNNING_TESTS.md +0 -4
  169. data/cucumber/paperclip_steps.rb +0 -6
  170. data/gemfiles/3.0.gemfile +0 -11
  171. data/gemfiles/3.1.gemfile +0 -11
  172. data/gemfiles/3.2.gemfile +0 -11
  173. data/lib/paperclip/attachment_options.rb +0 -9
  174. data/lib/paperclip/instance_methods.rb +0 -35
  175. data/test/attachment_options_test.rb +0 -27
  176. data/test/attachment_processing_test.rb +0 -29
  177. data/test/content_type_detector_test.rb +0 -40
  178. data/test/file_command_content_type_detector_test.rb +0 -25
  179. data/test/generator_test.rb +0 -80
  180. data/test/geometry_detector_test.rb +0 -24
  181. data/test/helper.rb +0 -199
  182. data/test/io_adapters/abstract_adapter_test.rb +0 -50
  183. data/test/io_adapters/file_adapter_test.rb +0 -100
  184. data/test/io_adapters/identity_adapter_test.rb +0 -8
  185. data/test/io_adapters/stringio_adapter_test.rb +0 -51
  186. data/test/io_adapters/uploaded_file_adapter_test.rb +0 -123
  187. data/test/io_adapters/uri_adapter_test.rb +0 -86
  188. data/test/matchers/have_attached_file_matcher_test.rb +0 -24
  189. data/test/matchers/validate_attachment_content_type_matcher_test.rb +0 -110
  190. data/test/matchers/validate_attachment_presence_matcher_test.rb +0 -47
  191. data/test/matchers/validate_attachment_size_matcher_test.rb +0 -86
  192. data/test/meta_class_test.rb +0 -32
  193. data/test/paperclip_missing_attachment_styles_test.rb +0 -94
  194. data/test/paperclip_test.rb +0 -259
  195. data/test/schema_test.rb +0 -200
  196. data/test/storage/fog_test.rb +0 -453
  197. data/test/storage/s3_live_test.rb +0 -179
  198. data/test/storage/s3_test.rb +0 -1236
  199. data/test/style_test.rb +0 -213
  200. data/test/support/mock_model.rb +0 -2
  201. data/test/tempfile_factory_test.rb +0 -13
  202. data/test/url_generator_test.rb +0 -187
  203. data/test/validators/attachment_content_type_validator_test.rb +0 -292
  204. data/test/validators_test.rb +0 -25
  205. /data/{test → spec}/database.yml +0 -0
  206. /data/{test → spec/support}/fixtures/12k.png +0 -0
  207. /data/{test → spec/support}/fixtures/50x50.png +0 -0
  208. /data/{test → spec/support}/fixtures/5k.png +0 -0
  209. /data/{test → spec/support}/fixtures/animated +0 -0
  210. /data/{test → spec/support}/fixtures/animated.gif +0 -0
  211. /data/{test → spec/support}/fixtures/animated.unknown +0 -0
  212. /data/{test → spec/support}/fixtures/bad.png +0 -0
  213. /data/{test → spec/support}/fixtures/fog.yml +0 -0
  214. /data/{test → spec/support}/fixtures/rotated.jpg +0 -0
  215. /data/{test → spec/support}/fixtures/s3.yml +0 -0
  216. /data/{test → spec/support}/fixtures/spaced file.png +0 -0
  217. /data/{test → spec/support}/fixtures/text.txt +0 -0
  218. /data/{test → spec/support}/fixtures/twopage.pdf +0 -0
  219. /data/{test → spec/support}/fixtures/uppercase.PNG +0 -0
  220. /data/{test → spec}/support/mock_interpolator.rb +0 -0
@@ -3,10 +3,10 @@ module Paperclip
3
3
  # Amazon's S3 file hosting service is a scalable, easy place to store files for
4
4
  # distribution. You can find out more about it at http://aws.amazon.com/s3
5
5
  #
6
- # To use Paperclip with S3, include the +aws-sdk+ gem in your Gemfile:
7
- # gem 'aws-sdk'
6
+ # To use Paperclip with S3, include the +aws-sdk-s3+ gem in your Gemfile:
7
+ # gem 'aws-sdk-s3'
8
8
  # There are a few S3-specific options for has_attached_file:
9
- # * +s3_credentials+: Takes a path, a File, or a Hash. The path (or File) must point
9
+ # * +s3_credentials+: Takes a path, a File, a Hash or a Proc. The path (or File) must point
10
10
  # to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon
11
11
  # gives you. You can 'environment-space' this just like you do to your
12
12
  # database.yml file, so different environments can use different accounts:
@@ -26,22 +26,33 @@ module Paperclip
26
26
  # put your bucket name in this file, instead of adding it to the code directly.
27
27
  # This is useful when you want the same account but a different bucket for
28
28
  # development versus production.
29
+ # When using a Proc it provides a single parameter which is the attachment itself. A
30
+ # method #instance is available on the attachment which will take you back to your
31
+ # code. eg.
32
+ # class User
33
+ # has_attached_file :download,
34
+ # :storage => :s3,
35
+ # :s3_credentials => Proc.new{|a| a.instance.s3_credentials }
36
+ #
37
+ # def s3_credentials
38
+ # {:bucket => "xxx", :access_key_id => "xxx", :secret_access_key => "xxx"}
39
+ # end
40
+ # end
29
41
  # * +s3_permissions+: This is a String that should be one of the "canned" access
30
42
  # policies that S3 provides (more information can be found here:
31
- # http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAccessPolicy.html)
32
- # The default for Paperclip is :public_read.
43
+ # http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html)
44
+ # The default for Paperclip is public-read.
33
45
  #
34
46
  # You can set permission on a per style bases by doing the following:
35
47
  # :s3_permissions => {
36
- # :original => :private
48
+ # :original => "private"
37
49
  # }
38
50
  # Or globally:
39
- # :s3_permissions => :private
51
+ # :s3_permissions => "private"
40
52
  #
41
- # * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
42
- # 'http', 'https', or an empty string to generate scheme-less URLs. Defaults to 'http'
43
- # when your :s3_permissions are :public_read (the default), and 'https' when your
44
- # :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.
45
56
  # * +s3_headers+: A hash of headers or a Proc. You may specify a hash such as
46
57
  # {'Expires' => 1.year.from_now.httpdate}. If you use a Proc, headers are determined at
47
58
  # runtime. Paperclip will call that Proc with attachment as the only argument.
@@ -49,11 +60,14 @@ module Paperclip
49
60
  # * +bucket+: This is the name of the S3 bucket that will store your files. Remember
50
61
  # that the bucket must be unique across all of Amazon S3. If the bucket does not exist
51
62
  # Paperclip will attempt to create it. The bucket name will not be interpolated.
52
- # 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.
53
64
  # Paperclip will call that Proc with attachment as the only argument.
54
65
  # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
55
66
  # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
56
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
57
71
  # * +url+: There are four options for the S3 url. You can choose to have the bucket's name
58
72
  # placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
59
73
  # You can also specify a CNAME (which requires the CNAME to be specified as
@@ -80,90 +94,99 @@ module Paperclip
80
94
  # to interpolate. Keys should be unique, like filenames, and despite the fact that
81
95
  # S3 (strictly speaking) does not support directories, you can still use a / to
82
96
  # separate parts of your file name.
83
- # * +s3_host_name+: If you are using your bucket in Tokyo region etc, write host_name.
97
+ # * +s3_host_name+: If you are using your bucket in Tokyo region
98
+ # etc, write host_name (e.g., 's3-ap-northeast-1.amazonaws.com').
99
+ # * +s3_region+: For aws-sdk-s3, s3_region is required.
84
100
  # * +s3_metadata+: These key/value pairs will be stored with the
85
101
  # object. This option works by prefixing each key with
86
102
  # "x-amz-meta-" before sending it as a header on the object
87
103
  # upload request. Can be defined both globally and within a style-specific hash.
88
104
  # * +s3_storage_class+: If this option is set to
89
- # <tt>:reduced_redundancy</tt>, the object will be stored using Reduced
90
- # Redundancy Storage. RRS enables customers to reduce their
105
+ # <tt>:REDUCED_REDUNDANCY</tt>, the object will be stored using Reduced
106
+ # Redundancy Storage. RRS enables customers to reduce their
91
107
  # costs by storing non-critical, reproducible data at lower
92
108
  # levels of redundancy than Amazon S3's standard storage.
109
+ # * +use_accelerate_endpoint+: Use accelerate endpoint
110
+ # http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
111
+ #
112
+ # You can set storage class on a per style bases by doing the following:
113
+ # :s3_storage_class => {
114
+ # :thumb => :REDUCED_REDUNDANCY
115
+ # }
116
+ #
117
+ # Or globally:
118
+ # :s3_storage_class => :REDUCED_REDUNDANCY
119
+ #
120
+ # Other storage classes, such as <tt>:STANDARD_IA</tt>, are also available—see the
121
+ # documentation for the <tt>aws-sdk-s3</tt> gem for the full list.
122
+
93
123
  module S3
94
124
  def self.extended base
95
125
  begin
96
- require 'aws-sdk'
126
+ require "aws-sdk-s3"
97
127
  rescue LoadError => e
98
- e.message << " (You may need to install the aws-sdk gem)"
128
+ e.message << " (You may need to install the aws-sdk-s3 gem)"
99
129
  raise e
100
- end unless defined?(AWS::Core)
101
-
102
- # Overriding AWS::Core::LogFormatter to make sure it return a UTF-8 string
103
- if AWS::VERSION >= "1.3.9"
104
- AWS::Core::LogFormatter.class_eval do
105
- def summarize_hash(hash)
106
- hash.map { |key, value| ":#{key}=>#{summarize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
107
- end
108
- end
109
- else
110
- AWS::Core::ClientLogging.class_eval do
111
- def sanitize_hash(hash)
112
- hash.map { |key, value| "#{sanitize_value(key)}=>#{sanitize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
113
- end
114
- end
115
130
  end
116
131
 
117
132
  base.instance_eval do
118
133
  @s3_options = @options[:s3_options] || {}
119
134
  @s3_permissions = set_permissions(@options[:s3_permissions])
120
- @s3_protocol = @options[:s3_protocol] ||
121
- Proc.new do |style, attachment|
122
- permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default])
123
- permission = permission.call(attachment, style) if permission.respond_to?(:call)
124
- (permission == :public_read) ? 'http' : 'https'
125
- end
135
+ @s3_protocol = @options[:s3_protocol] || "".freeze
126
136
  @s3_metadata = @options[:s3_metadata] || {}
127
137
  @s3_headers = {}
128
138
  merge_s3_headers(@options[:s3_headers], @s3_headers, @s3_metadata)
129
139
 
130
- @s3_headers[:storage_class] = @options[:s3_storage_class] if @options[:s3_storage_class]
140
+ @s3_storage_class = set_storage_class(@options[:s3_storage_class])
131
141
 
132
- @s3_server_side_encryption = :aes256
142
+ @s3_server_side_encryption = "AES256"
133
143
  if @options[:s3_server_side_encryption].blank?
134
144
  @s3_server_side_encryption = false
135
145
  end
136
146
  if @s3_server_side_encryption
137
- @s3_server_side_encryption = @options[:s3_server_side_encryption].to_s.upcase
147
+ @s3_server_side_encryption = @options[:s3_server_side_encryption]
138
148
  end
139
149
 
140
- unless @options[:url].to_s.match(/^:s3.*url$/) || @options[:url] == ":asset_host"
141
- @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/^:rails_root\/public\/system/, '')
142
- @options[:url] = ":s3_path_url"
150
+ unless @options[:url].to_s.match(/\A:s3.*url\z/) || @options[:url] == ":asset_host".freeze
151
+ @options[:path] = path_option.gsub(/:url/, @options[:url]).sub(/\A:rails_root\/public\/system/, "".freeze)
152
+ @options[:url] = ":s3_path_url".freeze
143
153
  end
144
154
  @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)
145
155
 
146
156
  @http_proxy = @options[:http_proxy] || nil
157
+
158
+ @use_accelerate_endpoint = @options[:use_accelerate_endpoint]
147
159
  end
148
160
 
149
161
  Paperclip.interpolates(:s3_alias_url) do |attachment, style|
150
- "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
162
+ protocol = attachment.s3_protocol(style, true)
163
+ host = attachment.s3_host_alias
164
+ path = attachment.path(style).
165
+ split("/")[attachment.s3_prefixes_in_alias..-1].
166
+ join("/").
167
+ sub(%r{\A/}, "".freeze)
168
+ "#{protocol}//#{host}/#{path}"
151
169
  end unless Paperclip::Interpolations.respond_to? :s3_alias_url
152
170
  Paperclip.interpolates(:s3_path_url) do |attachment, style|
153
- "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
171
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
154
172
  end unless Paperclip::Interpolations.respond_to? :s3_path_url
155
173
  Paperclip.interpolates(:s3_domain_url) do |attachment, style|
156
- "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
174
+ "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
157
175
  end unless Paperclip::Interpolations.respond_to? :s3_domain_url
158
176
  Paperclip.interpolates(:asset_host) do |attachment, style|
159
- "#{attachment.path(style).gsub(%r{^/}, "")}"
177
+ "#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
160
178
  end unless Paperclip::Interpolations.respond_to? :asset_host
161
179
  end
162
180
 
163
181
  def expiring_url(time = 3600, style_name = default_style)
164
- if path
165
- base_options = { :expires => time, :secure => use_secure_protocol?(style_name) }
166
- s3_object(style_name).url_for(:read, base_options.merge(s3_url_options)).to_s
182
+ if path(style_name)
183
+ base_options = { expires_in: time }
184
+ s3_object(style_name).presigned_url(
185
+ :get,
186
+ base_options.merge(s3_url_options),
187
+ ).to_s
188
+ else
189
+ url(style_name)
167
190
  end
168
191
  end
169
192
 
@@ -172,7 +195,17 @@ module Paperclip
172
195
  end
173
196
 
174
197
  def s3_host_name
175
- @options[:s3_host_name] || s3_credentials[:s3_host_name] || "s3.amazonaws.com"
198
+ host_name = @options[:s3_host_name]
199
+ host_name = host_name.call(self) if host_name.is_a?(Proc)
200
+
201
+ host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com".freeze
202
+ end
203
+
204
+ def s3_region
205
+ region = @options[:s3_region]
206
+ region = region.call(self) if region.is_a?(Proc)
207
+
208
+ region || s3_credentials[:s3_region]
176
209
  end
177
210
 
178
211
  def s3_host_alias
@@ -181,6 +214,10 @@ module Paperclip
181
214
  @s3_host_alias
182
215
  end
183
216
 
217
+ def s3_prefixes_in_alias
218
+ @s3_prefixes_in_alias ||= @options[:s3_prefixes_in_alias].to_i
219
+ end
220
+
184
221
  def s3_url_options
185
222
  s3_url_options = @options[:s3_url_options] || {}
186
223
  s3_url_options = s3_url_options.call(instance) if s3_url_options.respond_to?(:call)
@@ -195,7 +232,7 @@ module Paperclip
195
232
 
196
233
  def s3_interface
197
234
  @s3_interface ||= begin
198
- config = { :s3_endpoint => s3_host_name }
235
+ config = { region: s3_region }
199
236
 
200
237
  if using_http_proxy?
201
238
 
@@ -209,7 +246,9 @@ module Paperclip
209
246
  config[:proxy_uri] = URI::HTTP.build(proxy_opts)
210
247
  end
211
248
 
212
- [:access_key_id, :secret_access_key].each do |opt|
249
+ config[:use_accelerate_endpoint] = use_accelerate_endpoint?
250
+
251
+ [:access_key_id, :secret_access_key, :credential_provider, :credentials].each do |opt|
213
252
  config[opt] = s3_credentials[opt] if s3_credentials[opt]
214
253
  end
215
254
 
@@ -219,15 +258,23 @@ module Paperclip
219
258
 
220
259
  def obtain_s3_instance_for(options)
221
260
  instances = (Thread.current[:paperclip_s3_instances] ||= {})
222
- instances[options] ||= AWS::S3.new(options)
261
+ instances[options] ||= ::Aws::S3::Resource.new(options)
223
262
  end
224
263
 
225
264
  def s3_bucket
226
- @s3_bucket ||= s3_interface.buckets[bucket_name]
265
+ @s3_bucket ||= s3_interface.bucket(bucket_name)
266
+ end
267
+
268
+ def style_name_as_path(style_name)
269
+ path(style_name).sub(%r{\A/},'')
227
270
  end
228
271
 
229
272
  def s3_object style_name = default_style
230
- s3_bucket.objects[path(style_name).sub(%r{^/},'')]
273
+ s3_bucket.object style_name_as_path(style_name)
274
+ end
275
+
276
+ def use_accelerate_endpoint?
277
+ !!@use_accelerate_endpoint
231
278
  end
232
279
 
233
280
  def using_http_proxy?
@@ -252,14 +299,18 @@ module Paperclip
252
299
 
253
300
  def set_permissions permissions
254
301
  permissions = { :default => permissions } unless permissions.respond_to?(:merge)
255
- permissions.merge :default => (permissions[:default] || :public_read)
302
+ permissions.merge :default => (permissions[:default] || :"public-read")
303
+ end
304
+
305
+ def set_storage_class(storage_class)
306
+ storage_class = {:default => storage_class} unless storage_class.respond_to?(:merge)
307
+ storage_class
256
308
  end
257
309
 
258
310
  def parse_credentials creds
259
- creds = creds.respond_to?('call') ? creds.call(self) : creds
311
+ creds = creds.respond_to?(:call) ? creds.call(self) : creds
260
312
  creds = find_credentials(creds).stringify_keys
261
- env = Object.const_defined?(:Rails) ? Rails.env : nil
262
- (creds[env] || creds).symbolize_keys
313
+ (creds[RailsEnvironment.get] || creds).symbolize_keys
263
314
  end
264
315
 
265
316
  def exists?(style = default_style)
@@ -268,7 +319,7 @@ module Paperclip
268
319
  else
269
320
  false
270
321
  end
271
- rescue AWS::Errors::Base => e
322
+ rescue Aws::Errors::ServiceError => e
272
323
  false
273
324
  end
274
325
 
@@ -278,6 +329,10 @@ module Paperclip
278
329
  s3_permissions
279
330
  end
280
331
 
332
+ def s3_storage_class(style = default_style)
333
+ @s3_storage_class[style] || @s3_storage_class[:default]
334
+ end
335
+
281
336
  def s3_protocol(style = default_style, with_colon = false)
282
337
  protocol = @s3_protocol
283
338
  protocol = protocol.call(style, self) if protocol.respond_to?(:call)
@@ -290,36 +345,49 @@ module Paperclip
290
345
  end
291
346
 
292
347
  def create_bucket
293
- s3_interface.buckets.create(bucket_name)
348
+ s3_interface.bucket(bucket_name).create
294
349
  end
295
350
 
296
351
  def flush_writes #:nodoc:
297
352
  @queued_for_write.each do |style, file|
353
+ retries = 0
298
354
  begin
299
355
  log("saving #{path(style)}")
300
- acl = @s3_permissions[style] || @s3_permissions[:default]
301
- acl = acl.call(self, style) if acl.respond_to?(:call)
302
356
  write_options = {
303
357
  :content_type => file.content_type,
304
- :acl => acl
358
+ :acl => s3_permissions(style)
305
359
  }
360
+
361
+ # add storage class for this style if defined
362
+ storage_class = s3_storage_class(style)
363
+ write_options.merge!(:storage_class => storage_class) if storage_class
364
+
306
365
  if @s3_server_side_encryption
307
366
  write_options[:server_side_encryption] = @s3_server_side_encryption
308
367
  end
309
368
 
310
- style_specific_options = @options[:styles][style]
311
- if style_specific_options.is_a?(Hash)
312
- merge_s3_headers( style_specific_options[:s3_headers], @s3_headers, @s3_metadata) if style_specific_options.has_key?(:s3_headers)
313
- @s3_metadata.merge!(style_specific_options[:s3_metadata]) if style_specific_options.has_key?(:s3_metadata)
369
+ style_specific_options = styles[style]
370
+
371
+ if style_specific_options
372
+ merge_s3_headers( style_specific_options[:s3_headers], @s3_headers, @s3_metadata) if style_specific_options[:s3_headers]
373
+ @s3_metadata.merge!(style_specific_options[:s3_metadata]) if style_specific_options[:s3_metadata]
314
374
  end
315
375
 
316
376
  write_options[:metadata] = @s3_metadata unless @s3_metadata.empty?
317
377
  write_options.merge!(@s3_headers)
318
378
 
319
- s3_object(style).write(file, write_options)
320
- rescue AWS::S3::Errors::NoSuchBucket => e
379
+ s3_object(style).upload_file(file.path, write_options)
380
+ rescue ::Aws::S3::Errors::NoSuchBucket
321
381
  create_bucket
322
382
  retry
383
+ rescue ::Aws::S3::Errors::SlowDown
384
+ retries += 1
385
+ if retries <= 5
386
+ sleep((2 ** retries) * 0.5)
387
+ retry
388
+ else
389
+ raise
390
+ end
323
391
  ensure
324
392
  file.rewind
325
393
  end
@@ -334,8 +402,8 @@ module Paperclip
334
402
  @queued_for_delete.each do |path|
335
403
  begin
336
404
  log("deleting #{path}")
337
- s3_bucket.objects[path.sub(%r{^/},'')].delete
338
- rescue AWS::Errors::Base => e
405
+ s3_bucket.object(path.sub(%r{\A/}, "")).delete
406
+ rescue Aws::Errors::ServiceError => e
339
407
  # Ignore this.
340
408
  end
341
409
  end
@@ -344,11 +412,12 @@ module Paperclip
344
412
 
345
413
  def copy_to_local_file(style, local_dest_path)
346
414
  log("copying #{path(style)} to local file #{local_dest_path}")
347
- local_file = ::File.open(local_dest_path, 'wb')
348
- file = s3_object(style)
349
- local_file.write(file.read)
350
- local_file.close
351
- rescue AWS::Errors::Base => e
415
+ ::File.open(local_dest_path, 'wb') do |local_file|
416
+ s3_object(style).get do |chunk|
417
+ local_file.write(chunk)
418
+ end
419
+ end
420
+ rescue Aws::Errors::ServiceError => e
352
421
  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
353
422
  false
354
423
  end
@@ -363,8 +432,10 @@ module Paperclip
363
432
  YAML::load(ERB.new(File.read(creds)).result)
364
433
  when Hash
365
434
  creds
435
+ when NilClass
436
+ {}
366
437
  else
367
- raise ArgumentError, "Credentials are not a path, file, proc, or hash."
438
+ raise ArgumentError, "Credentials given are not a path, file, proc, or hash."
368
439
  end
369
440
  end
370
441
 
@@ -377,10 +448,10 @@ module Paperclip
377
448
  http_headers = http_headers.call(instance) if http_headers.respond_to?(:call)
378
449
  http_headers.inject({}) do |headers,(name,value)|
379
450
  case name.to_s
380
- when /^x-amz-meta-(.*)/i
451
+ when /\Ax-amz-meta-(.*)/i
381
452
  s3_metadata[$1.downcase] = value
382
453
  else
383
- s3_headers[name.to_s.downcase.sub(/^x-amz-/,'').tr("-","_").to_sym] = value
454
+ s3_headers[name.to_s.downcase.sub(/\Ax-amz-/,'').tr("-","_").to_sym] = value
384
455
  end
385
456
  end
386
457
  end
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Paperclip
3
2
  # The Style class holds the definition of a thumbnail style, applying
4
3
  # whatever processing is required to normalize the definition and delaying
@@ -29,7 +28,7 @@ module Paperclip
29
28
  @geometry, @format = [definition, nil].flatten[0..1]
30
29
  @other_args = {}
31
30
  end
32
- @format = nil if @format.blank?
31
+ @format = default_format if @format.blank?
33
32
  end
34
33
 
35
34
  # retrieves from the attachment the processors defined in the has_attached_file call
@@ -71,7 +70,7 @@ module Paperclip
71
70
  # Arguments other than the standard geometry, format etc are just passed through from
72
71
  # initialization and any procs are called here, just before post-processing.
73
72
  def processor_options
74
- args = {}
73
+ args = {:style => name}
75
74
  @other_args.each do |k,v|
76
75
  args[k] = v.respond_to?(:call) ? v.call(attachment) : v
77
76
  end
@@ -99,5 +98,11 @@ module Paperclip
99
98
  end
100
99
  end
101
100
 
101
+ # defaults to default format (nil by default)
102
+ def default_format
103
+ base = attachment.options[:default_format]
104
+ base.respond_to?(:call) ? base.call(attachment, name) : base
105
+ end
106
+
102
107
  end
103
108
  end
@@ -1,9 +1,7 @@
1
1
  module Paperclip
2
2
  class TempfileFactory
3
3
 
4
- ILLEGAL_FILENAME_CHARACTERS = /^~/
5
-
6
- def generate(name)
4
+ def generate(name = random_name)
7
5
  @name = name
8
6
  file = Tempfile.new([basename, extension])
9
7
  file.binmode
@@ -15,7 +13,11 @@ module Paperclip
15
13
  end
16
14
 
17
15
  def basename
18
- File.basename(@name, extension).gsub(ILLEGAL_FILENAME_CHARACTERS, '_')
16
+ Digest::MD5.hexdigest(File.basename(@name, extension))
17
+ end
18
+
19
+ def random_name
20
+ SecureRandom.uuid
19
21
  end
20
22
  end
21
23
  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,29 +26,29 @@ 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
- geometry = options[:geometry] # this is not an option
32
- @file = file
33
+ geometry = options[:geometry].to_s
33
34
  @crop = geometry[-1,1] == '#'
34
- @target_geometry = (options[:string_geometry_parser] || Geometry).parse(geometry)
35
- @current_geometry = (options[:file_geometry_parser] || Geometry).from_file(@file)
35
+ @target_geometry = options.fetch(:string_geometry_parser, Geometry).parse(geometry)
36
+ @current_geometry = options.fetch(:file_geometry_parser, Geometry).from_file(@file)
36
37
  @source_file_options = options[:source_file_options]
37
38
  @convert_options = options[:convert_options]
38
- @whiny = options[:whiny].nil? ? true : options[:whiny]
39
+ @whiny = options.fetch(:whiny, true)
39
40
  @format = options[:format]
40
- @animated = options[:animated].nil? ? true : options[:animated]
41
- @auto_orient = options[:auto_orient].nil? ? true : options[:auto_orient]
41
+ @animated = options.fetch(:animated, true)
42
+ @auto_orient = options.fetch(:auto_orient, true)
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,10 +78,18 @@ 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
- rescue Cocaine::ExitStatusError => e
82
- raise Paperclip::Error, "There was an error processing the thumbnail for #{@basename}" if @whiny
83
- rescue Cocaine::CommandNotFoundError => e
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
+ )
87
+ rescue Terrapin::ExitStatusError => e
88
+ if @whiny
89
+ message = "There was an error processing the thumbnail for #{@basename}:\n" + e.message
90
+ raise Paperclip::Error, message
91
+ end
92
+ rescue Terrapin::CommandNotFoundError => e
84
93
  raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.")
85
94
  end
86
95
 
@@ -96,22 +105,29 @@ module Paperclip
96
105
  trans << "-auto-orient" if auto_orient
97
106
  trans << "-resize" << %["#{scale}"] unless scale.nil? || scale.empty?
98
107
  trans << "-crop" << %["#{crop}"] << "+repage" if crop
108
+ trans << '-layers "optimize"' if animated?
99
109
  trans
100
110
  end
101
111
 
102
112
  protected
103
113
 
104
- # Return true if the format is animated
114
+ def multi_frame_format?
115
+ MULTI_FRAME_FORMATS.include? @current_format
116
+ end
117
+
105
118
  def animated?
106
119
  @animated && (ANIMATED_FORMATS.include?(@format.to_s) || @format.blank?) && identified_as_animated?
107
120
  end
108
121
 
109
122
  # Return true if ImageMagick's +identify+ returns an animated format
110
123
  def identified_as_animated?
111
- ANIMATED_FORMATS.include? identify("-format %m :file", :file => "#{@file.path}[0]").to_s.downcase.strip
112
- rescue Cocaine::ExitStatusError => e
124
+ if @identified_as_animated.nil?
125
+ @identified_as_animated = ANIMATED_FORMATS.include? identify("-format %m :file", :file => "#{@file.path}[0]").to_s.downcase.strip
126
+ end
127
+ @identified_as_animated
128
+ rescue Terrapin::ExitStatusError => e
113
129
  raise Paperclip::Error, "There was an error running `identify` for #{@basename}" if @whiny
114
- rescue Cocaine::CommandNotFoundError => e
130
+ rescue Terrapin::CommandNotFoundError => e
115
131
  raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
116
132
  end
117
133
  end