paperclip-fix 4.3.7

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 (197) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.hound.yml +1066 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +22 -0
  6. data/Appraisals +11 -0
  7. data/CONTRIBUTING.md +75 -0
  8. data/Gemfile +21 -0
  9. data/LICENSE +24 -0
  10. data/NEWS +420 -0
  11. data/README.md +979 -0
  12. data/RELEASING.md +17 -0
  13. data/Rakefile +44 -0
  14. data/UPGRADING +14 -0
  15. data/cucumber/paperclip_steps.rb +6 -0
  16. data/features/basic_integration.feature +80 -0
  17. data/features/migration.feature +94 -0
  18. data/features/rake_tasks.feature +62 -0
  19. data/features/step_definitions/attachment_steps.rb +110 -0
  20. data/features/step_definitions/html_steps.rb +15 -0
  21. data/features/step_definitions/rails_steps.rb +236 -0
  22. data/features/step_definitions/s3_steps.rb +14 -0
  23. data/features/step_definitions/web_steps.rb +107 -0
  24. data/features/support/env.rb +11 -0
  25. data/features/support/fakeweb.rb +13 -0
  26. data/features/support/file_helpers.rb +34 -0
  27. data/features/support/fixtures/boot_config.txt +15 -0
  28. data/features/support/fixtures/gemfile.txt +5 -0
  29. data/features/support/fixtures/preinitializer.txt +20 -0
  30. data/features/support/paths.rb +28 -0
  31. data/features/support/rails.rb +63 -0
  32. data/features/support/selectors.rb +19 -0
  33. data/gemfiles/3.2.gemfile +19 -0
  34. data/gemfiles/4.1.gemfile +19 -0
  35. data/gemfiles/4.2.gemfile +19 -0
  36. data/lib/generators/paperclip/USAGE +8 -0
  37. data/lib/generators/paperclip/paperclip_generator.rb +30 -0
  38. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  39. data/lib/paperclip/attachment.rb +608 -0
  40. data/lib/paperclip/attachment_registry.rb +59 -0
  41. data/lib/paperclip/callbacks.rb +40 -0
  42. data/lib/paperclip/content_type_detector.rb +79 -0
  43. data/lib/paperclip/deprecations.rb +42 -0
  44. data/lib/paperclip/errors.rb +32 -0
  45. data/lib/paperclip/file_command_content_type_detector.rb +30 -0
  46. data/lib/paperclip/filename_cleaner.rb +16 -0
  47. data/lib/paperclip/geometry.rb +158 -0
  48. data/lib/paperclip/geometry_detector_factory.rb +48 -0
  49. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  50. data/lib/paperclip/glue.rb +17 -0
  51. data/lib/paperclip/has_attached_file.rb +109 -0
  52. data/lib/paperclip/helpers.rb +56 -0
  53. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  54. data/lib/paperclip/interpolations.rb +197 -0
  55. data/lib/paperclip/io_adapters/abstract_adapter.rb +47 -0
  56. data/lib/paperclip/io_adapters/attachment_adapter.rb +36 -0
  57. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  58. data/lib/paperclip/io_adapters/empty_string_adapter.rb +18 -0
  59. data/lib/paperclip/io_adapters/file_adapter.rb +22 -0
  60. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +15 -0
  61. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -0
  62. data/lib/paperclip/io_adapters/nil_adapter.rb +34 -0
  63. data/lib/paperclip/io_adapters/registry.rb +32 -0
  64. data/lib/paperclip/io_adapters/stringio_adapter.rb +33 -0
  65. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +42 -0
  66. data/lib/paperclip/io_adapters/uri_adapter.rb +63 -0
  67. data/lib/paperclip/locales/de.yml +18 -0
  68. data/lib/paperclip/locales/en.yml +18 -0
  69. data/lib/paperclip/locales/es.yml +18 -0
  70. data/lib/paperclip/locales/ja.yml +18 -0
  71. data/lib/paperclip/locales/pt-BR.yml +18 -0
  72. data/lib/paperclip/locales/zh-CN.yml +18 -0
  73. data/lib/paperclip/locales/zh-HK.yml +18 -0
  74. data/lib/paperclip/locales/zh-TW.yml +18 -0
  75. data/lib/paperclip/logger.rb +21 -0
  76. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  77. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +100 -0
  78. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  79. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +96 -0
  80. data/lib/paperclip/matchers.rb +64 -0
  81. data/lib/paperclip/media_type_spoof_detector.rb +89 -0
  82. data/lib/paperclip/missing_attachment_styles.rb +79 -0
  83. data/lib/paperclip/processor.rb +48 -0
  84. data/lib/paperclip/processor_helpers.rb +50 -0
  85. data/lib/paperclip/rails_environment.rb +25 -0
  86. data/lib/paperclip/railtie.rb +31 -0
  87. data/lib/paperclip/schema.rb +83 -0
  88. data/lib/paperclip/storage/filesystem.rb +90 -0
  89. data/lib/paperclip/storage/fog.rb +241 -0
  90. data/lib/paperclip/storage/s3.rb +440 -0
  91. data/lib/paperclip/storage.rb +3 -0
  92. data/lib/paperclip/style.rb +109 -0
  93. data/lib/paperclip/tempfile.rb +43 -0
  94. data/lib/paperclip/tempfile_factory.rb +23 -0
  95. data/lib/paperclip/thumbnail.rb +121 -0
  96. data/lib/paperclip/url_generator.rb +72 -0
  97. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  98. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  99. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  100. data/lib/paperclip/validators/attachment_presence_validator.rb +30 -0
  101. data/lib/paperclip/validators/attachment_size_validator.rb +115 -0
  102. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
  103. data/lib/paperclip/validators.rb +74 -0
  104. data/lib/paperclip/version.rb +3 -0
  105. data/lib/paperclip.rb +213 -0
  106. data/lib/tasks/paperclip.rake +127 -0
  107. data/paperclip.gemspec +51 -0
  108. data/shoulda_macros/paperclip.rb +134 -0
  109. data/spec/database.yml +4 -0
  110. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  111. data/spec/paperclip/attachment_processing_spec.rb +82 -0
  112. data/spec/paperclip/attachment_registry_spec.rb +130 -0
  113. data/spec/paperclip/attachment_spec.rb +1494 -0
  114. data/spec/paperclip/content_type_detector_spec.rb +48 -0
  115. data/spec/paperclip/deprecations_spec.rb +65 -0
  116. data/spec/paperclip/file_command_content_type_detector_spec.rb +26 -0
  117. data/spec/paperclip/filename_cleaner_spec.rb +14 -0
  118. data/spec/paperclip/geometry_detector_spec.rb +39 -0
  119. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  120. data/spec/paperclip/geometry_spec.rb +255 -0
  121. data/spec/paperclip/glue_spec.rb +44 -0
  122. data/spec/paperclip/has_attached_file_spec.rb +142 -0
  123. data/spec/paperclip/integration_spec.rb +667 -0
  124. data/spec/paperclip/interpolations_spec.rb +262 -0
  125. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +78 -0
  126. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +139 -0
  127. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +83 -0
  128. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  129. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  130. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +104 -0
  131. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  132. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  133. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  134. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  135. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  136. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +127 -0
  137. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  138. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +99 -0
  139. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  140. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  141. data/spec/paperclip/media_type_spoof_detector_spec.rb +79 -0
  142. data/spec/paperclip/meta_class_spec.rb +30 -0
  143. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  144. data/spec/paperclip/paperclip_spec.rb +222 -0
  145. data/spec/paperclip/plural_cache_spec.rb +37 -0
  146. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  147. data/spec/paperclip/processor_spec.rb +26 -0
  148. data/spec/paperclip/rails_environment_spec.rb +33 -0
  149. data/spec/paperclip/rake_spec.rb +103 -0
  150. data/spec/paperclip/schema_spec.rb +248 -0
  151. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  152. data/spec/paperclip/storage/fog_spec.rb +535 -0
  153. data/spec/paperclip/storage/s3_live_spec.rb +182 -0
  154. data/spec/paperclip/storage/s3_spec.rb +1526 -0
  155. data/spec/paperclip/style_spec.rb +255 -0
  156. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  157. data/spec/paperclip/thumbnail_spec.rb +500 -0
  158. data/spec/paperclip/url_generator_spec.rb +211 -0
  159. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  160. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  161. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  162. data/spec/paperclip/validators/attachment_size_validator_spec.rb +229 -0
  163. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  164. data/spec/paperclip/validators_spec.rb +164 -0
  165. data/spec/spec_helper.rb +43 -0
  166. data/spec/support/assertions.rb +71 -0
  167. data/spec/support/deprecations.rb +9 -0
  168. data/spec/support/fake_model.rb +25 -0
  169. data/spec/support/fake_rails.rb +12 -0
  170. data/spec/support/fixtures/12k.png +0 -0
  171. data/spec/support/fixtures/50x50.png +0 -0
  172. data/spec/support/fixtures/5k.png +0 -0
  173. data/spec/support/fixtures/animated +0 -0
  174. data/spec/support/fixtures/animated.gif +0 -0
  175. data/spec/support/fixtures/animated.unknown +0 -0
  176. data/spec/support/fixtures/bad.png +1 -0
  177. data/spec/support/fixtures/empty.html +1 -0
  178. data/spec/support/fixtures/empty.xlsx +0 -0
  179. data/spec/support/fixtures/fog.yml +8 -0
  180. data/spec/support/fixtures/rotated.jpg +0 -0
  181. data/spec/support/fixtures/s3.yml +8 -0
  182. data/spec/support/fixtures/spaced file.jpg +0 -0
  183. data/spec/support/fixtures/spaced file.png +0 -0
  184. data/spec/support/fixtures/text.txt +1 -0
  185. data/spec/support/fixtures/twopage.pdf +0 -0
  186. data/spec/support/fixtures/uppercase.PNG +0 -0
  187. data/spec/support/matchers/accept.rb +5 -0
  188. data/spec/support/matchers/exist.rb +5 -0
  189. data/spec/support/matchers/have_column.rb +23 -0
  190. data/spec/support/mock_attachment.rb +22 -0
  191. data/spec/support/mock_interpolator.rb +24 -0
  192. data/spec/support/mock_url_generator_builder.rb +27 -0
  193. data/spec/support/model_reconstruction.rb +60 -0
  194. data/spec/support/rails_helpers.rb +7 -0
  195. data/spec/support/test_data.rb +13 -0
  196. data/spec/support/version_helper.rb +9 -0
  197. metadata +606 -0
@@ -0,0 +1,50 @@
1
+ module Paperclip
2
+ module ProcessorHelpers
3
+ class NoSuchProcessor < StandardError; end
4
+
5
+ def processor(name) #:nodoc:
6
+ @known_processors ||= {}
7
+ if @known_processors[name.to_s]
8
+ @known_processors[name.to_s]
9
+ else
10
+ name = name.to_s.camelize
11
+ load_processor(name) unless Paperclip.const_defined?(name)
12
+ processor = Paperclip.const_get(name)
13
+ @known_processors[name.to_s] = processor
14
+ end
15
+ end
16
+
17
+ def load_processor(name)
18
+ if defined?(Rails.root) && Rails.root
19
+ filename = "#{name.to_s.underscore}.rb"
20
+ directories = %w(lib/paperclip lib/paperclip_processors)
21
+
22
+ required = directories.map do |directory|
23
+ pathname = File.expand_path(Rails.root.join(directory, filename))
24
+ file_exists = File.exist?(pathname)
25
+ require pathname if file_exists
26
+ file_exists
27
+ end
28
+
29
+ raise LoadError, "Could not find the '#{name}' processor in any of these paths: #{directories.join(', ')}" unless required.any?
30
+ end
31
+ end
32
+
33
+ def clear_processors!
34
+ @known_processors.try(:clear)
35
+ end
36
+
37
+ # You can add your own processor via the Paperclip configuration. Normally
38
+ # Paperclip will load all processors from the
39
+ # Rails.root/lib/paperclip_processors directory, but here you can add any
40
+ # existing class using this mechanism.
41
+ #
42
+ # Paperclip.configure do |c|
43
+ # c.register_processor :watermarker, WatermarkingProcessor.new
44
+ # end
45
+ def register_processor(name, processor)
46
+ @known_processors ||= {}
47
+ @known_processors[name.to_s] = processor
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ module Paperclip
2
+ class RailsEnvironment
3
+ def self.get
4
+ new.get
5
+ end
6
+
7
+ def get
8
+ if rails_exists? && rails_environment_exists?
9
+ Rails.env
10
+ else
11
+ nil
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def rails_exists?
18
+ Object.const_defined?(:Rails)
19
+ end
20
+
21
+ def rails_environment_exists?
22
+ Rails.respond_to?(:env)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ require 'paperclip'
2
+ require 'paperclip/schema'
3
+
4
+ module Paperclip
5
+ require 'rails'
6
+
7
+ class Railtie < Rails::Railtie
8
+ initializer 'paperclip.insert_into_active_record' do |app|
9
+ ActiveSupport.on_load :active_record do
10
+ Paperclip::Railtie.insert
11
+ end
12
+
13
+ if app.config.respond_to?(:paperclip_defaults)
14
+ Paperclip::Attachment.default_options.merge!(app.config.paperclip_defaults)
15
+ end
16
+ end
17
+
18
+ rake_tasks { load "tasks/paperclip.rake" }
19
+ end
20
+
21
+ class Railtie
22
+ def self.insert
23
+ Paperclip.options[:logger] = Rails.logger
24
+
25
+ if defined?(ActiveRecord)
26
+ Paperclip.options[:logger] = ActiveRecord::Base.logger
27
+ ActiveRecord::Base.send(:include, Paperclip::Glue)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,83 @@
1
+ require 'active_support/deprecation'
2
+
3
+ module Paperclip
4
+ # Provides helper methods that can be used in migrations.
5
+ module Schema
6
+ COLUMNS = {:file_name => :string,
7
+ :content_type => :string,
8
+ :file_size => :integer,
9
+ :updated_at => :datetime}
10
+
11
+ def self.included(base)
12
+ ActiveRecord::ConnectionAdapters::Table.send :include, TableDefinition
13
+ ActiveRecord::ConnectionAdapters::TableDefinition.send :include, TableDefinition
14
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, Statements
15
+
16
+ if defined?(ActiveRecord::Migration::CommandRecorder) # Rails 3.1+
17
+ ActiveRecord::Migration::CommandRecorder.send :include, CommandRecorder
18
+ end
19
+ end
20
+
21
+ module Statements
22
+ def add_attachment(table_name, *attachment_names)
23
+ raise ArgumentError, "Please specify attachment name in your add_attachment call in your migration." if attachment_names.empty?
24
+
25
+ options = attachment_names.extract_options!
26
+
27
+ attachment_names.each do |attachment_name|
28
+ COLUMNS.each_pair do |column_name, column_type|
29
+ column_options = options.merge(options[column_name.to_sym] || {})
30
+ add_column(table_name, "#{attachment_name}_#{column_name}", column_type, column_options)
31
+ end
32
+ end
33
+ end
34
+
35
+ def remove_attachment(table_name, *attachment_names)
36
+ raise ArgumentError, "Please specify attachment name in your remove_attachment call in your migration." if attachment_names.empty?
37
+
38
+ options = attachment_names.extract_options!
39
+
40
+ attachment_names.each do |attachment_name|
41
+ COLUMNS.each_pair do |column_name, column_type|
42
+ column_options = options.merge(options[column_name.to_sym] || {})
43
+ remove_column(table_name, "#{attachment_name}_#{column_name}")
44
+ end
45
+ end
46
+ end
47
+
48
+ def drop_attached_file(*args)
49
+ ActiveSupport::Deprecation.warn "Method `drop_attached_file` in the migration has been deprecated and will be replaced by `remove_attachment`."
50
+ remove_attachment(*args)
51
+ end
52
+ end
53
+
54
+ module TableDefinition
55
+ def attachment(*attachment_names)
56
+ options = attachment_names.extract_options!
57
+ attachment_names.each do |attachment_name|
58
+ COLUMNS.each_pair do |column_name, column_type|
59
+ column_options = options.merge(options[column_name.to_sym] || {})
60
+ column("#{attachment_name}_#{column_name}", column_type, column_options)
61
+ end
62
+ end
63
+ end
64
+
65
+ def has_attached_file(*attachment_names)
66
+ ActiveSupport::Deprecation.warn "Method `t.has_attached_file` in the migration has been deprecated and will be replaced by `t.attachment`."
67
+ attachment(*attachment_names)
68
+ end
69
+ end
70
+
71
+ module CommandRecorder
72
+ def add_attachment(*args)
73
+ record(:add_attachment, args)
74
+ end
75
+
76
+ private
77
+
78
+ def invert_add_attachment(args)
79
+ [:remove_attachment, args]
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,90 @@
1
+ module Paperclip
2
+ module Storage
3
+ # The default place to store attachments is in the filesystem. Files on the local
4
+ # filesystem can be very easily served by Apache without requiring a hit to your app.
5
+ # They also can be processed more easily after they've been saved, as they're just
6
+ # normal files. There are two Filesystem-specific options for has_attached_file:
7
+ # * +path+: The location of the repository of attachments on disk. This can (and, in
8
+ # almost all cases, should) be coordinated with the value of the +url+ option to
9
+ # allow files to be saved into a place where Apache can serve them without
10
+ # hitting your app. Defaults to
11
+ # ":rails_root/public/:attachment/:id/:style/:basename.:extension"
12
+ # By default this places the files in the app's public directory which can be served
13
+ # directly. If you are using capistrano for deployment, a good idea would be to
14
+ # make a symlink to the capistrano-created system directory from inside your app's
15
+ # public directory.
16
+ # See Paperclip::Attachment#interpolate for more information on variable interpolaton.
17
+ # :path => "/var/app/attachments/:class/:id/:style/:basename.:extension"
18
+ # * +override_file_permissions+: This allows you to override the file permissions for files
19
+ # saved by paperclip. If you set this to an explicit octal value (0755, 0644, etc) then
20
+ # that value will be used to set the permissions for an uploaded file. The default is 0666.
21
+ # If you set :override_file_permissions to false, the chmod will be skipped. This allows
22
+ # you to use paperclip on filesystems that don't understand unix file permissions, and has the
23
+ # added benefit of using the storage directories default umask on those that do.
24
+ module Filesystem
25
+ def self.extended base
26
+ end
27
+
28
+ def exists?(style_name = default_style)
29
+ if original_filename
30
+ File.exist?(path(style_name))
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ def flush_writes #:nodoc:
37
+ @queued_for_write.each do |style_name, file|
38
+ FileUtils.mkdir_p(File.dirname(path(style_name)))
39
+ begin
40
+ FileUtils.mv(file.path, path(style_name))
41
+ rescue SystemCallError
42
+ File.open(path(style_name), "wb") do |new_file|
43
+ while chunk = file.read(16 * 1024)
44
+ new_file.write(chunk)
45
+ end
46
+ end
47
+ end
48
+ unless @options[:override_file_permissions] == false
49
+ resolved_chmod = (@options[:override_file_permissions] &~ 0111) || (0666 &~ File.umask)
50
+ FileUtils.chmod( resolved_chmod, path(style_name) )
51
+ end
52
+ file.rewind
53
+ end
54
+
55
+ after_flush_writes # allows attachment to clean up temp files
56
+
57
+ @queued_for_write = {}
58
+ end
59
+
60
+ def flush_deletes #:nodoc:
61
+ @queued_for_delete.each do |path|
62
+ begin
63
+ log("deleting #{path}")
64
+ FileUtils.rm(path) if File.exist?(path)
65
+ rescue Errno::ENOENT => e
66
+ # ignore file-not-found, let everything else pass
67
+ end
68
+ begin
69
+ while(true)
70
+ path = File.dirname(path)
71
+ FileUtils.rmdir(path)
72
+ break if File.exist?(path) # Ruby 1.9.2 does not raise if the removal failed.
73
+ end
74
+ rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR, Errno::EACCES
75
+ # Stop trying to remove parent directories
76
+ rescue SystemCallError => e
77
+ log("There was an unexpected error while deleting directories: #{e.class}")
78
+ # Ignore it
79
+ end
80
+ end
81
+ @queued_for_delete = []
82
+ end
83
+
84
+ def copy_to_local_file(style, local_dest_path)
85
+ FileUtils.cp(path(style), local_dest_path)
86
+ end
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,241 @@
1
+ module Paperclip
2
+ module Storage
3
+ # fog is a modern and versatile cloud computing library for Ruby.
4
+ # Among others, it supports Amazon S3 to store your files. In
5
+ # contrast to the outdated AWS-S3 gem it is actively maintained and
6
+ # supports multiple locations.
7
+ # Amazon's S3 file hosting service is a scalable, easy place to
8
+ # store files for distribution. You can find out more about it at
9
+ # http://aws.amazon.com/s3 There are a few fog-specific options for
10
+ # has_attached_file, which will be explained using S3 as an example:
11
+ # * +fog_credentials+: Takes a Hash with your credentials. For S3,
12
+ # you can use the following format:
13
+ # aws_access_key_id: '<your aws_access_key_id>'
14
+ # aws_secret_access_key: '<your aws_secret_access_key>'
15
+ # provider: 'AWS'
16
+ # region: 'eu-west-1'
17
+ # scheme: 'https'
18
+ # * +fog_directory+: This is the name of the S3 bucket that will
19
+ # store your files. Remember that the bucket must be unique across
20
+ # all of Amazon S3. If the bucket does not exist, Paperclip will
21
+ # attempt to create it.
22
+ # * +fog_file*: This can be hash or lambda returning hash. The
23
+ # value is used as base properties for new uploaded file.
24
+ # * +path+: This is the key under the bucket in which the file will
25
+ # be stored. The URL will be constructed from the bucket and the
26
+ # path. This is what you will want to interpolate. Keys should be
27
+ # unique, like filenames, and despite the fact that S3 (strictly
28
+ # speaking) does not support directories, you can still use a / to
29
+ # separate parts of your file name.
30
+ # * +fog_public+: (optional, defaults to true) Should the uploaded
31
+ # files be public or not? (true/false)
32
+ # * +fog_host+: (optional) The fully-qualified domain name (FQDN)
33
+ # that is the alias to the S3 domain of your bucket, e.g.
34
+ # 'http://images.example.com'. This can also be used in
35
+ # conjunction with Cloudfront (http://aws.amazon.com/cloudfront)
36
+ # * +fog_options+: (optional) A hash of options that are passed
37
+ # to fog when the file is created. For example, you could set
38
+ # the multipart-chunk size to 100MB with a hash:
39
+ # { :multipart_chunk_size => 104857600 }
40
+
41
+ module Fog
42
+ def self.extended base
43
+ begin
44
+ require 'fog'
45
+ rescue LoadError => e
46
+ e.message << " (You may need to install the fog gem)"
47
+ raise e
48
+ end unless defined?(Fog)
49
+
50
+ base.instance_eval do
51
+ unless @options[:url].to_s.match(/\A:fog.*url\Z/)
52
+ @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/\A:rails_root\/public\/system\//, '')
53
+ @options[:url] = ':fog_public_url'
54
+ end
55
+ Paperclip.interpolates(:fog_public_url) do |attachment, style|
56
+ attachment.public_url(style)
57
+ end unless Paperclip::Interpolations.respond_to? :fog_public_url
58
+ end
59
+ end
60
+
61
+ AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX = /\A(?:[a-z]|\d(?!\d{0,2}(?:\.\d{1,3}){3}\Z))(?:[a-z0-9]|\.(?![\.\-])|\-(?![\.])){1,61}[a-z0-9]\Z/
62
+
63
+ def exists?(style = default_style)
64
+ if original_filename
65
+ !!directory.files.head(path(style))
66
+ else
67
+ false
68
+ end
69
+ end
70
+
71
+ def fog_credentials
72
+ @fog_credentials ||= parse_credentials(@options[:fog_credentials])
73
+ end
74
+
75
+ def fog_file
76
+ @fog_file ||= begin
77
+ value = @options[:fog_file]
78
+ if !value
79
+ {}
80
+ elsif value.respond_to?(:call)
81
+ value.call(self)
82
+ else
83
+ value
84
+ end
85
+ end
86
+ end
87
+
88
+ def fog_public(style = default_style)
89
+ if @options.has_key?(:fog_public)
90
+ if @options[:fog_public].respond_to?(:has_key?) && @options[:fog_public].has_key?(style)
91
+ @options[:fog_public][style]
92
+ else
93
+ @options[:fog_public]
94
+ end
95
+ else
96
+ true
97
+ end
98
+ end
99
+
100
+ def flush_writes
101
+ for style, file in @queued_for_write do
102
+ log("saving #{path(style)}")
103
+ retried = false
104
+ begin
105
+ attributes = fog_file.merge(
106
+ :body => file,
107
+ :key => path(style),
108
+ :public => fog_public(style),
109
+ :content_type => file.content_type
110
+ )
111
+ attributes.merge!(@options[:fog_options]) if @options[:fog_options]
112
+ directory.files.create(attributes)
113
+ rescue Excon::Errors::NotFound
114
+ raise if retried
115
+ retried = true
116
+ directory.save
117
+ retry
118
+ ensure
119
+ file.rewind
120
+ end
121
+ end
122
+
123
+ after_flush_writes # allows attachment to clean up temp files
124
+
125
+ @queued_for_write = {}
126
+ end
127
+
128
+ def flush_deletes
129
+ for path in @queued_for_delete do
130
+ log("deleting #{path}")
131
+ directory.files.new(:key => path).destroy
132
+ end
133
+ @queued_for_delete = []
134
+ end
135
+
136
+ def public_url(style = default_style)
137
+ if @options[:fog_host]
138
+ "#{dynamic_fog_host_for_style(style)}/#{path(style)}"
139
+ else
140
+ if fog_credentials[:provider] == 'AWS'
141
+ "#{scheme}://#{host_name_for_directory}/#{path(style)}"
142
+ else
143
+ directory.files.new(:key => path(style)).public_url
144
+ end
145
+ end
146
+ end
147
+
148
+ def expiring_url(time = (Time.now + 3600), style_name = default_style)
149
+ time = convert_time(time)
150
+ http_url_method = "get_#{scheme}_url"
151
+ if path(style_name) && directory.files.respond_to?(http_url_method)
152
+ expiring_url = directory.files.public_send(http_url_method, path(style_name), time)
153
+
154
+ if @options[:fog_host]
155
+ expiring_url.gsub!(/#{host_name_for_directory}/, dynamic_fog_host_for_style(style_name))
156
+ end
157
+ else
158
+ expiring_url = url(style_name)
159
+ end
160
+
161
+ return expiring_url
162
+ end
163
+
164
+ def parse_credentials(creds)
165
+ creds = find_credentials(creds).stringify_keys
166
+ (creds[RailsEnvironment.get] || creds).symbolize_keys
167
+ end
168
+
169
+ def copy_to_local_file(style, local_dest_path)
170
+ log("copying #{path(style)} to local file #{local_dest_path}")
171
+ ::File.open(local_dest_path, 'wb') do |local_file|
172
+ file = directory.files.get(path(style))
173
+ local_file.write(file.body)
174
+ end
175
+ rescue ::Fog::Errors::Error => e
176
+ warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
177
+ false
178
+ end
179
+
180
+ private
181
+
182
+ def convert_time(time)
183
+ if time.is_a?(Fixnum)
184
+ time = Time.now + time
185
+ end
186
+ time
187
+ end
188
+
189
+ def dynamic_fog_host_for_style(style)
190
+ if @options[:fog_host].respond_to?(:call)
191
+ @options[:fog_host].call(self)
192
+ else
193
+ (@options[:fog_host] =~ /%d/) ? @options[:fog_host] % (path(style).hash % 4) : @options[:fog_host]
194
+ end
195
+ end
196
+
197
+ def host_name_for_directory
198
+ if @options[:fog_directory].to_s =~ Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX
199
+ "#{@options[:fog_directory]}.s3.amazonaws.com"
200
+ else
201
+ "s3.amazonaws.com/#{@options[:fog_directory]}"
202
+ end
203
+ end
204
+
205
+ def find_credentials(creds)
206
+ case creds
207
+ when File
208
+ YAML::load(ERB.new(File.read(creds.path)).result)
209
+ when String, Pathname
210
+ YAML::load(ERB.new(File.read(creds)).result)
211
+ when Hash
212
+ creds
213
+ else
214
+ if creds.respond_to?(:call)
215
+ creds.call(self)
216
+ else
217
+ raise ArgumentError, "Credentials are not a path, file, hash or proc."
218
+ end
219
+ end
220
+ end
221
+
222
+ def connection
223
+ @connection ||= ::Fog::Storage.new(fog_credentials)
224
+ end
225
+
226
+ def directory
227
+ dir = if @options[:fog_directory].respond_to?(:call)
228
+ @options[:fog_directory].call(self)
229
+ else
230
+ @options[:fog_directory]
231
+ end
232
+
233
+ @directory ||= connection.directories.new(:key => dir)
234
+ end
235
+
236
+ def scheme
237
+ @scheme ||= fog_credentials[:scheme] || 'https'
238
+ end
239
+ end
240
+ end
241
+ end