paperclip_jk 5.0.0.beta2

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 (190) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +21 -0
  3. data/.hound.yml +1066 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +26 -0
  6. data/Appraisals +27 -0
  7. data/CONTRIBUTING.md +86 -0
  8. data/Gemfile +15 -0
  9. data/LICENSE +24 -0
  10. data/NEWS +424 -0
  11. data/README.md +999 -0
  12. data/RELEASING.md +17 -0
  13. data/Rakefile +44 -0
  14. data/UPGRADING +17 -0
  15. data/features/basic_integration.feature +81 -0
  16. data/features/migration.feature +70 -0
  17. data/features/rake_tasks.feature +62 -0
  18. data/features/step_definitions/attachment_steps.rb +110 -0
  19. data/features/step_definitions/html_steps.rb +15 -0
  20. data/features/step_definitions/rails_steps.rb +230 -0
  21. data/features/step_definitions/s3_steps.rb +14 -0
  22. data/features/step_definitions/web_steps.rb +107 -0
  23. data/features/support/env.rb +11 -0
  24. data/features/support/fakeweb.rb +13 -0
  25. data/features/support/file_helpers.rb +34 -0
  26. data/features/support/fixtures/boot_config.txt +15 -0
  27. data/features/support/fixtures/gemfile.txt +5 -0
  28. data/features/support/fixtures/preinitializer.txt +20 -0
  29. data/features/support/paths.rb +28 -0
  30. data/features/support/rails.rb +63 -0
  31. data/features/support/selectors.rb +19 -0
  32. data/gemfiles/4.2.awsv2.0.gemfile +17 -0
  33. data/gemfiles/4.2.awsv2.1.gemfile +17 -0
  34. data/gemfiles/4.2.awsv2.gemfile +20 -0
  35. data/gemfiles/5.0.awsv2.0.gemfile +17 -0
  36. data/gemfiles/5.0.awsv2.1.gemfile +17 -0
  37. data/gemfiles/5.0.awsv2.gemfile +25 -0
  38. data/lib/generators/paperclip/USAGE +8 -0
  39. data/lib/generators/paperclip/paperclip_generator.rb +30 -0
  40. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  41. data/lib/paperclip.rb +211 -0
  42. data/lib/paperclip/attachment.rb +610 -0
  43. data/lib/paperclip/attachment_registry.rb +60 -0
  44. data/lib/paperclip/callbacks.rb +42 -0
  45. data/lib/paperclip/content_type_detector.rb +80 -0
  46. data/lib/paperclip/errors.rb +34 -0
  47. data/lib/paperclip/file_command_content_type_detector.rb +30 -0
  48. data/lib/paperclip/filename_cleaner.rb +16 -0
  49. data/lib/paperclip/geometry.rb +158 -0
  50. data/lib/paperclip/geometry_detector_factory.rb +48 -0
  51. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  52. data/lib/paperclip/glue.rb +17 -0
  53. data/lib/paperclip/has_attached_file.rb +115 -0
  54. data/lib/paperclip/helpers.rb +60 -0
  55. data/lib/paperclip/interpolations.rb +197 -0
  56. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  57. data/lib/paperclip/io_adapters/abstract_adapter.rb +47 -0
  58. data/lib/paperclip/io_adapters/attachment_adapter.rb +36 -0
  59. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  60. data/lib/paperclip/io_adapters/empty_string_adapter.rb +18 -0
  61. data/lib/paperclip/io_adapters/file_adapter.rb +22 -0
  62. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +15 -0
  63. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -0
  64. data/lib/paperclip/io_adapters/nil_adapter.rb +34 -0
  65. data/lib/paperclip/io_adapters/registry.rb +32 -0
  66. data/lib/paperclip/io_adapters/stringio_adapter.rb +33 -0
  67. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +42 -0
  68. data/lib/paperclip/io_adapters/uri_adapter.rb +44 -0
  69. data/lib/paperclip/locales/en.yml +18 -0
  70. data/lib/paperclip/logger.rb +21 -0
  71. data/lib/paperclip/matchers.rb +64 -0
  72. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  73. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +100 -0
  74. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  75. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +96 -0
  76. data/lib/paperclip/media_type_spoof_detector.rb +89 -0
  77. data/lib/paperclip/missing_attachment_styles.rb +79 -0
  78. data/lib/paperclip/processor.rb +48 -0
  79. data/lib/paperclip/processor_helpers.rb +50 -0
  80. data/lib/paperclip/rails_environment.rb +25 -0
  81. data/lib/paperclip/railtie.rb +31 -0
  82. data/lib/paperclip/schema.rb +77 -0
  83. data/lib/paperclip/storage.rb +4 -0
  84. data/lib/paperclip/storage/database.rb +140 -0
  85. data/lib/paperclip/storage/filesystem.rb +90 -0
  86. data/lib/paperclip/storage/fog.rb +244 -0
  87. data/lib/paperclip/storage/s3.rb +442 -0
  88. data/lib/paperclip/style.rb +109 -0
  89. data/lib/paperclip/tempfile.rb +43 -0
  90. data/lib/paperclip/tempfile_factory.rb +23 -0
  91. data/lib/paperclip/thumbnail.rb +121 -0
  92. data/lib/paperclip/url_generator.rb +72 -0
  93. data/lib/paperclip/validators.rb +74 -0
  94. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  95. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  96. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  97. data/lib/paperclip/validators/attachment_presence_validator.rb +30 -0
  98. data/lib/paperclip/validators/attachment_size_validator.rb +109 -0
  99. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
  100. data/lib/paperclip/version.rb +3 -0
  101. data/lib/tasks/paperclip.rake +127 -0
  102. data/paperclip.gemspec +54 -0
  103. data/shoulda_macros/paperclip.rb +134 -0
  104. data/spec/database.yml +4 -0
  105. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  106. data/spec/paperclip/attachment_processing_spec.rb +80 -0
  107. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  108. data/spec/paperclip/attachment_spec.rb +1517 -0
  109. data/spec/paperclip/content_type_detector_spec.rb +48 -0
  110. data/spec/paperclip/file_command_content_type_detector_spec.rb +26 -0
  111. data/spec/paperclip/filename_cleaner_spec.rb +14 -0
  112. data/spec/paperclip/geometry_detector_spec.rb +39 -0
  113. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  114. data/spec/paperclip/geometry_spec.rb +255 -0
  115. data/spec/paperclip/glue_spec.rb +44 -0
  116. data/spec/paperclip/has_attached_file_spec.rb +158 -0
  117. data/spec/paperclip/integration_spec.rb +668 -0
  118. data/spec/paperclip/interpolations_spec.rb +262 -0
  119. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +78 -0
  120. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +139 -0
  121. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +83 -0
  122. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  123. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  124. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +113 -0
  125. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  126. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  127. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  128. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  129. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  130. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +102 -0
  131. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  132. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +109 -0
  133. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  134. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  135. data/spec/paperclip/media_type_spoof_detector_spec.rb +70 -0
  136. data/spec/paperclip/meta_class_spec.rb +30 -0
  137. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  138. data/spec/paperclip/paperclip_spec.rb +192 -0
  139. data/spec/paperclip/plural_cache_spec.rb +37 -0
  140. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  141. data/spec/paperclip/processor_spec.rb +26 -0
  142. data/spec/paperclip/rails_environment_spec.rb +33 -0
  143. data/spec/paperclip/rake_spec.rb +103 -0
  144. data/spec/paperclip/schema_spec.rb +248 -0
  145. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  146. data/spec/paperclip/storage/fog_spec.rb +545 -0
  147. data/spec/paperclip/storage/s3_live_spec.rb +186 -0
  148. data/spec/paperclip/storage/s3_spec.rb +1583 -0
  149. data/spec/paperclip/style_spec.rb +255 -0
  150. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  151. data/spec/paperclip/thumbnail_spec.rb +500 -0
  152. data/spec/paperclip/url_generator_spec.rb +211 -0
  153. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  154. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  155. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  156. data/spec/paperclip/validators/attachment_size_validator_spec.rb +235 -0
  157. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  158. data/spec/paperclip/validators_spec.rb +164 -0
  159. data/spec/spec_helper.rb +45 -0
  160. data/spec/support/assertions.rb +78 -0
  161. data/spec/support/fake_model.rb +25 -0
  162. data/spec/support/fake_rails.rb +12 -0
  163. data/spec/support/fixtures/12k.png +0 -0
  164. data/spec/support/fixtures/50x50.png +0 -0
  165. data/spec/support/fixtures/5k.png +0 -0
  166. data/spec/support/fixtures/animated +0 -0
  167. data/spec/support/fixtures/animated.gif +0 -0
  168. data/spec/support/fixtures/animated.unknown +0 -0
  169. data/spec/support/fixtures/bad.png +1 -0
  170. data/spec/support/fixtures/empty.html +1 -0
  171. data/spec/support/fixtures/empty.xlsx +0 -0
  172. data/spec/support/fixtures/fog.yml +8 -0
  173. data/spec/support/fixtures/rotated.jpg +0 -0
  174. data/spec/support/fixtures/s3.yml +8 -0
  175. data/spec/support/fixtures/spaced file.jpg +0 -0
  176. data/spec/support/fixtures/spaced file.png +0 -0
  177. data/spec/support/fixtures/text.txt +1 -0
  178. data/spec/support/fixtures/twopage.pdf +0 -0
  179. data/spec/support/fixtures/uppercase.PNG +0 -0
  180. data/spec/support/matchers/accept.rb +5 -0
  181. data/spec/support/matchers/exist.rb +5 -0
  182. data/spec/support/matchers/have_column.rb +23 -0
  183. data/spec/support/mock_attachment.rb +22 -0
  184. data/spec/support/mock_interpolator.rb +24 -0
  185. data/spec/support/mock_url_generator_builder.rb +27 -0
  186. data/spec/support/model_reconstruction.rb +68 -0
  187. data/spec/support/reporting.rb +11 -0
  188. data/spec/support/test_data.rb +13 -0
  189. data/spec/support/version_helper.rb +9 -0
  190. metadata +713 -0
@@ -0,0 +1,442 @@
1
+ module Paperclip
2
+ module Storage
3
+ # Amazon's S3 file hosting service is a scalable, easy place to store files for
4
+ # distribution. You can find out more about it at http://aws.amazon.com/s3
5
+ #
6
+ # To use Paperclip with S3, include the +aws-sdk+ gem in your Gemfile:
7
+ # gem 'aws-sdk'
8
+ # There are a few S3-specific options for has_attached_file:
9
+ # * +s3_credentials+: Takes a path, a File, a Hash or a Proc. The path (or File) must point
10
+ # to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon
11
+ # gives you. You can 'environment-space' this just like you do to your
12
+ # database.yml file, so different environments can use different accounts:
13
+ # development:
14
+ # access_key_id: 123...
15
+ # secret_access_key: 123...
16
+ # test:
17
+ # access_key_id: abc...
18
+ # secret_access_key: abc...
19
+ # production:
20
+ # access_key_id: 456...
21
+ # secret_access_key: 456...
22
+ # This is not required, however, and the file may simply look like this:
23
+ # access_key_id: 456...
24
+ # secret_access_key: 456...
25
+ # In which case, those access keys will be used in all environments. You can also
26
+ # put your bucket name in this file, instead of adding it to the code directly.
27
+ # This is useful when you want the same account but a different bucket for
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
41
+ # * +s3_permissions+: This is a String that should be one of the "canned" access
42
+ # policies that S3 provides (more information can be found here:
43
+ # http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html)
44
+ # The default for Paperclip is public-read.
45
+ #
46
+ # You can set permission on a per style bases by doing the following:
47
+ # :s3_permissions => {
48
+ # :original => :private
49
+ # }
50
+ # Or globally:
51
+ # :s3_permissions => :private
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.
57
+ # * +s3_headers+: A hash of headers or a Proc. You may specify a hash such as
58
+ # {'Expires' => 1.year.from_now.httpdate}. If you use a Proc, headers are determined at
59
+ # runtime. Paperclip will call that Proc with attachment as the only argument.
60
+ # Can be defined both globally and within a style-specific hash.
61
+ # * +bucket+: This is the name of the S3 bucket that will store your files. Remember
62
+ # that the bucket must be unique across all of Amazon S3. If the bucket does not exist
63
+ # 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 its name at runtime.
65
+ # Paperclip will call that Proc with attachment as the only argument.
66
+ # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
67
+ # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
68
+ # link in the +url+ entry for more information about S3 domains and buckets.
69
+ # * +url+: There are four options for the S3 url. You can choose to have the bucket's name
70
+ # placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
71
+ # You can also specify a CNAME (which requires the CNAME to be specified as
72
+ # :s3_alias_url. You can read more about CNAMEs and S3 at
73
+ # http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html
74
+ # Normally, this won't matter in the slightest and you can leave the default (which is
75
+ # path-style, or :s3_path_url). But in some cases paths don't work and you need to use
76
+ # the domain-style (:s3_domain_url). Anything else here will be treated like path-style.
77
+ #
78
+ # Notes:
79
+ # * The value of this option is a string, not a symbol.
80
+ # <b>right:</b> <tt>":s3_domain_url"</tt>
81
+ # <b>wrong:</b> <tt>:s3_domain_url</tt>
82
+ # * If you use a CNAME for use with CloudFront, you can NOT specify https as your
83
+ # :s3_protocol;
84
+ # This is *not supported* by S3/CloudFront. Finally, when using the host
85
+ # alias, the :bucket parameter is ignored, as the hostname is used as the bucket name
86
+ # by S3. The fourth option for the S3 url is :asset_host, which uses Rails' built-in
87
+ # asset_host settings.
88
+ # * To get the full url from a paperclip'd object, use the
89
+ # image_path helper; this is what image_tag uses to generate the url for an img tag.
90
+ # * +path+: This is the key under the bucket in which the file will be stored. The
91
+ # URL will be constructed from the bucket and the path. This is what you will want
92
+ # to interpolate. Keys should be unique, like filenames, and despite the fact that
93
+ # S3 (strictly speaking) does not support directories, you can still use a / to
94
+ # separate parts of your file name.
95
+ # * +s3_host_name+: If you are using your bucket in Tokyo region etc, write host_name.
96
+ # * +s3_region+: For aws-sdk v2, s3_region is required.
97
+ # * +s3_metadata+: These key/value pairs will be stored with the
98
+ # object. This option works by prefixing each key with
99
+ # "x-amz-meta-" before sending it as a header on the object
100
+ # upload request. Can be defined both globally and within a style-specific hash.
101
+ # * +s3_storage_class+: If this option is set to
102
+ # <tt>:reduced_redundancy</tt>, the object will be stored using Reduced
103
+ # Redundancy Storage. RRS enables customers to reduce their
104
+ # costs by storing non-critical, reproducible data at lower
105
+ # levels of redundancy than Amazon S3's standard storage.
106
+ #
107
+ # You can set storage class on a per style bases by doing the following:
108
+ # :s3_storage_class => {
109
+ # :thumb => :reduced_reduncancy
110
+ # }
111
+ # Or globally:
112
+ # :s3_storage_class => :reduced_redundancy
113
+
114
+ module S3
115
+ def self.extended base
116
+ begin
117
+ require 'aws-sdk'
118
+ rescue LoadError => e
119
+ e.message << " (You may need to install the aws-sdk gem)"
120
+ raise e
121
+ end
122
+ if Gem::Version.new(Aws::VERSION) >= Gem::Version.new(2) &&
123
+ Gem::Version.new(Aws::VERSION) <= Gem::Version.new("2.0.33")
124
+ raise LoadError, "paperclip does not support aws-sdk versions 2.0.0 - 2.0.33. Please upgrade aws-sdk to a newer version."
125
+ end
126
+
127
+ base.instance_eval do
128
+ @s3_options = @options[:s3_options] || {}
129
+ @s3_permissions = set_permissions(@options[:s3_permissions])
130
+ @s3_protocol = @options[:s3_protocol] ||
131
+ Proc.new do |style, attachment|
132
+ permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default])
133
+ permission = permission.call(attachment, style) if permission.respond_to?(:call)
134
+ (permission == :"public-read") ? 'http'.freeze : 'https'.freeze
135
+ end
136
+ @s3_metadata = @options[:s3_metadata] || {}
137
+ @s3_headers = {}
138
+ merge_s3_headers(@options[:s3_headers], @s3_headers, @s3_metadata)
139
+
140
+ @s3_storage_class = set_storage_class(@options[:s3_storage_class])
141
+
142
+ @s3_server_side_encryption = "AES256"
143
+ if @options[:s3_server_side_encryption].blank?
144
+ @s3_server_side_encryption = false
145
+ end
146
+ if @s3_server_side_encryption
147
+ @s3_server_side_encryption = @options[:s3_server_side_encryption]
148
+ end
149
+
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
153
+ end
154
+ @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)
155
+
156
+ @http_proxy = @options[:http_proxy] || nil
157
+ end
158
+
159
+ Paperclip.interpolates(:s3_alias_url) do |attachment, style|
160
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
161
+ end unless Paperclip::Interpolations.respond_to? :s3_alias_url
162
+ Paperclip.interpolates(:s3_path_url) do |attachment, style|
163
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
164
+ end unless Paperclip::Interpolations.respond_to? :s3_path_url
165
+ Paperclip.interpolates(:s3_domain_url) do |attachment, style|
166
+ "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
167
+ end unless Paperclip::Interpolations.respond_to? :s3_domain_url
168
+ Paperclip.interpolates(:asset_host) do |attachment, style|
169
+ "#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
170
+ end unless Paperclip::Interpolations.respond_to? :asset_host
171
+ end
172
+
173
+ def expiring_url(time = 3600, style_name = default_style)
174
+ if path(style_name)
175
+ base_options = { expires_in: time }
176
+ s3_object(style_name).presigned_url(
177
+ :get,
178
+ base_options.merge(s3_url_options),
179
+ ).to_s
180
+ else
181
+ url(style_name)
182
+ end
183
+ end
184
+
185
+ def s3_credentials
186
+ @s3_credentials ||= parse_credentials(@options[:s3_credentials])
187
+ end
188
+
189
+ def s3_host_name
190
+ host_name = @options[:s3_host_name]
191
+ host_name = host_name.call(self) if host_name.is_a?(Proc)
192
+
193
+ host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com".freeze
194
+ end
195
+
196
+ def s3_region
197
+ region = @options[:s3_region]
198
+ region = region.call(self) if region.is_a?(Proc)
199
+
200
+ region || s3_credentials[:s3_region]
201
+ end
202
+
203
+ def s3_host_alias
204
+ @s3_host_alias = @options[:s3_host_alias]
205
+ @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.respond_to?(:call)
206
+ @s3_host_alias
207
+ end
208
+
209
+ def s3_url_options
210
+ s3_url_options = @options[:s3_url_options] || {}
211
+ s3_url_options = s3_url_options.call(instance) if s3_url_options.respond_to?(:call)
212
+ s3_url_options
213
+ end
214
+
215
+ def bucket_name
216
+ @bucket = @options[:bucket] || s3_credentials[:bucket]
217
+ @bucket = @bucket.call(self) if @bucket.respond_to?(:call)
218
+ @bucket or raise ArgumentError, "missing required :bucket option"
219
+ end
220
+
221
+ def s3_interface
222
+ @s3_interface ||= begin
223
+ config = { region: s3_region }
224
+
225
+ if using_http_proxy?
226
+
227
+ proxy_opts = { :host => http_proxy_host }
228
+ proxy_opts[:port] = http_proxy_port if http_proxy_port
229
+ if http_proxy_user
230
+ userinfo = http_proxy_user.to_s
231
+ userinfo += ":#{http_proxy_password}" if http_proxy_password
232
+ proxy_opts[:userinfo] = userinfo
233
+ end
234
+ config[:proxy_uri] = URI::HTTP.build(proxy_opts)
235
+ end
236
+
237
+ [:access_key_id, :secret_access_key, :credential_provider, :credentials].each do |opt|
238
+ config[opt] = s3_credentials[opt] if s3_credentials[opt]
239
+ end
240
+
241
+ obtain_s3_instance_for(config.merge(@s3_options))
242
+ end
243
+ end
244
+
245
+ def obtain_s3_instance_for(options)
246
+ instances = (Thread.current[:paperclip_s3_instances] ||= {})
247
+ instances[options] ||= ::Aws::S3::Resource.new(options)
248
+ end
249
+
250
+ def s3_bucket
251
+ @s3_bucket ||= s3_interface.bucket(bucket_name)
252
+ end
253
+
254
+ def style_name_as_path(style_name)
255
+ path(style_name).sub(%r{\A/},'')
256
+ end
257
+
258
+ def s3_object style_name = default_style
259
+ s3_bucket.object style_name_as_path(style_name)
260
+ end
261
+
262
+ def using_http_proxy?
263
+ !!@http_proxy
264
+ end
265
+
266
+ def http_proxy_host
267
+ using_http_proxy? ? @http_proxy[:host] : nil
268
+ end
269
+
270
+ def http_proxy_port
271
+ using_http_proxy? ? @http_proxy[:port] : nil
272
+ end
273
+
274
+ def http_proxy_user
275
+ using_http_proxy? ? @http_proxy[:user] : nil
276
+ end
277
+
278
+ def http_proxy_password
279
+ using_http_proxy? ? @http_proxy[:password] : nil
280
+ end
281
+
282
+ def set_permissions permissions
283
+ permissions = { :default => permissions } unless permissions.respond_to?(:merge)
284
+ permissions.merge :default => (permissions[:default] || :"public-read")
285
+ end
286
+
287
+ def set_storage_class(storage_class)
288
+ storage_class = {:default => storage_class} unless storage_class.respond_to?(:merge)
289
+ storage_class
290
+ end
291
+
292
+ def parse_credentials creds
293
+ creds = creds.respond_to?(:call) ? creds.call(self) : creds
294
+ creds = find_credentials(creds).stringify_keys
295
+ (creds[RailsEnvironment.get] || creds).symbolize_keys
296
+ end
297
+
298
+ def exists?(style = default_style)
299
+ if original_filename
300
+ s3_object(style).exists?
301
+ else
302
+ false
303
+ end
304
+ rescue Aws::Errors::ServiceError => e
305
+ false
306
+ end
307
+
308
+ def s3_permissions(style = default_style)
309
+ s3_permissions = @s3_permissions[style] || @s3_permissions[:default]
310
+ s3_permissions = s3_permissions.call(self, style) if s3_permissions.respond_to?(:call)
311
+ s3_permissions
312
+ end
313
+
314
+ def s3_storage_class(style = default_style)
315
+ @s3_storage_class[style] || @s3_storage_class[:default]
316
+ end
317
+
318
+ def s3_protocol(style = default_style, with_colon = false)
319
+ protocol = @s3_protocol
320
+ protocol = protocol.call(style, self) if protocol.respond_to?(:call)
321
+
322
+ if with_colon && !protocol.empty?
323
+ "#{protocol}:"
324
+ else
325
+ protocol.to_s
326
+ end
327
+ end
328
+
329
+ def create_bucket
330
+ s3_interface.bucket(bucket_name).create
331
+ end
332
+
333
+ def flush_writes #:nodoc:
334
+ @queued_for_write.each do |style, file|
335
+ retries = 0
336
+ begin
337
+ log("saving #{path(style)}")
338
+ write_options = {
339
+ :content_type => file.content_type,
340
+ :acl => s3_permissions(style)
341
+ }
342
+
343
+ # add storage class for this style if defined
344
+ storage_class = s3_storage_class(style)
345
+ write_options.merge!(:storage_class => storage_class) if storage_class
346
+
347
+ if @s3_server_side_encryption
348
+ write_options[:server_side_encryption] = @s3_server_side_encryption
349
+ end
350
+
351
+ style_specific_options = styles[style]
352
+
353
+ if style_specific_options
354
+ merge_s3_headers( style_specific_options[:s3_headers], @s3_headers, @s3_metadata) if style_specific_options[:s3_headers]
355
+ @s3_metadata.merge!(style_specific_options[:s3_metadata]) if style_specific_options[:s3_metadata]
356
+ end
357
+
358
+ write_options[:metadata] = @s3_metadata unless @s3_metadata.empty?
359
+ write_options.merge!(@s3_headers)
360
+
361
+ s3_object(style).upload_file(file.path, write_options)
362
+ rescue ::Aws::S3::Errors::NoSuchBucket
363
+ create_bucket
364
+ retry
365
+ rescue ::Aws::S3::Errors::SlowDown
366
+ retries += 1
367
+ if retries <= 5
368
+ sleep((2 ** retries) * 0.5)
369
+ retry
370
+ else
371
+ raise
372
+ end
373
+ ensure
374
+ file.rewind
375
+ end
376
+ end
377
+
378
+ after_flush_writes # allows attachment to clean up temp files
379
+
380
+ @queued_for_write = {}
381
+ end
382
+
383
+ def flush_deletes #:nodoc:
384
+ @queued_for_delete.each do |path|
385
+ begin
386
+ log("deleting #{path}")
387
+ s3_bucket.object(path.sub(%r{\A/}, "")).delete
388
+ rescue Aws::Errors::ServiceError => e
389
+ # Ignore this.
390
+ end
391
+ end
392
+ @queued_for_delete = []
393
+ end
394
+
395
+ def copy_to_local_file(style, local_dest_path)
396
+ log("copying #{path(style)} to local file #{local_dest_path}")
397
+ ::File.open(local_dest_path, 'wb') do |local_file|
398
+ s3_object(style).get do |chunk|
399
+ local_file.write(chunk)
400
+ end
401
+ end
402
+ rescue Aws::Errors::ServiceError => e
403
+ warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
404
+ false
405
+ end
406
+
407
+ private
408
+
409
+ def find_credentials creds
410
+ case creds
411
+ when File
412
+ YAML::load(ERB.new(File.read(creds.path)).result)
413
+ when String, Pathname
414
+ YAML::load(ERB.new(File.read(creds)).result)
415
+ when Hash
416
+ creds
417
+ when NilClass
418
+ {}
419
+ else
420
+ raise ArgumentError, "Credentials given are not a path, file, proc, or hash."
421
+ end
422
+ end
423
+
424
+ def use_secure_protocol?(style_name)
425
+ s3_protocol(style_name) == "https"
426
+ end
427
+
428
+ def merge_s3_headers(http_headers, s3_headers, s3_metadata)
429
+ return if http_headers.nil?
430
+ http_headers = http_headers.call(instance) if http_headers.respond_to?(:call)
431
+ http_headers.inject({}) do |headers,(name,value)|
432
+ case name.to_s
433
+ when /\Ax-amz-meta-(.*)/i
434
+ s3_metadata[$1.downcase] = value
435
+ else
436
+ s3_headers[name.to_s.downcase.sub(/\Ax-amz-/,'').tr("-","_").to_sym] = value
437
+ end
438
+ end
439
+ end
440
+ end
441
+ end
442
+ end
@@ -0,0 +1,109 @@
1
+ # encoding: utf-8
2
+ module Paperclip
3
+ # The Style class holds the definition of a thumbnail style, applying
4
+ # whatever processing is required to normalize the definition and delaying
5
+ # the evaluation of block parameters until useful context is available.
6
+
7
+ class Style
8
+
9
+ attr_reader :name, :attachment, :format
10
+
11
+ # Creates a Style object. +name+ is the name of the attachment,
12
+ # +definition+ is the style definition from has_attached_file, which
13
+ # can be string, array or hash
14
+ def initialize name, definition, attachment
15
+ @name = name
16
+ @attachment = attachment
17
+ if definition.is_a? Hash
18
+ @geometry = definition.delete(:geometry)
19
+ @format = definition.delete(:format)
20
+ @processors = definition.delete(:processors)
21
+ @convert_options = definition.delete(:convert_options)
22
+ @source_file_options = definition.delete(:source_file_options)
23
+ @other_args = definition
24
+ elsif definition.is_a? String
25
+ @geometry = definition
26
+ @format = nil
27
+ @other_args = {}
28
+ else
29
+ @geometry, @format = [definition, nil].flatten[0..1]
30
+ @other_args = {}
31
+ end
32
+ @format = default_format if @format.blank?
33
+ end
34
+
35
+ # retrieves from the attachment the processors defined in the has_attached_file call
36
+ # (which method (in the attachment) will call any supplied procs)
37
+ # There is an important change of interface here: a style rule can set its own processors
38
+ # by default we behave as before, though.
39
+ # if a proc has been supplied, we call it here
40
+ def processors
41
+ @processors.respond_to?(:call) ? @processors.call(attachment.instance) : (@processors || attachment.processors)
42
+ end
43
+
44
+ # retrieves from the attachment the whiny setting
45
+ def whiny
46
+ attachment.whiny
47
+ end
48
+
49
+ # returns true if we're inclined to grumble
50
+ def whiny?
51
+ !!whiny
52
+ end
53
+
54
+ def convert_options
55
+ @convert_options.respond_to?(:call) ? @convert_options.call(attachment.instance) :
56
+ (@convert_options || attachment.send(:extra_options_for, name))
57
+ end
58
+
59
+ def source_file_options
60
+ @source_file_options.respond_to?(:call) ? @source_file_options.call(attachment.instance) :
61
+ (@source_file_options || attachment.send(:extra_source_file_options_for, name))
62
+ end
63
+
64
+ # returns the geometry string for this style
65
+ # if a proc has been supplied, we call it here
66
+ def geometry
67
+ @geometry.respond_to?(:call) ? @geometry.call(attachment.instance) : @geometry
68
+ end
69
+
70
+ # Supplies the hash of options that processors expect to receive as their second argument
71
+ # Arguments other than the standard geometry, format etc are just passed through from
72
+ # initialization and any procs are called here, just before post-processing.
73
+ def processor_options
74
+ args = {:style => name}
75
+ @other_args.each do |k,v|
76
+ args[k] = v.respond_to?(:call) ? v.call(attachment) : v
77
+ end
78
+ [:processors, :geometry, :format, :whiny, :convert_options, :source_file_options].each do |k|
79
+ (arg = send(k)) && args[k] = arg
80
+ end
81
+ args
82
+ end
83
+
84
+ # Supports getting and setting style properties with hash notation to ensure backwards-compatibility
85
+ # eg. @attachment.styles[:large][:geometry]@ will still work
86
+ def [](key)
87
+ if [:name, :convert_options, :whiny, :processors, :geometry, :format, :animated, :source_file_options].include?(key)
88
+ send(key)
89
+ elsif defined? @other_args[key]
90
+ @other_args[key]
91
+ end
92
+ end
93
+
94
+ def []=(key, value)
95
+ if [:name, :convert_options, :whiny, :processors, :geometry, :format, :animated, :source_file_options].include?(key)
96
+ send("#{key}=".intern, value)
97
+ else
98
+ @other_args[key] = value
99
+ end
100
+ end
101
+
102
+ # defaults to default format (nil by default)
103
+ def default_format
104
+ base = attachment.options[:default_format]
105
+ base.respond_to?(:call) ? base.call(attachment, name) : base
106
+ end
107
+
108
+ end
109
+ end