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,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,77 @@
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
+ ActiveRecord::Migration::CommandRecorder.send :include, CommandRecorder
16
+ end
17
+
18
+ module Statements
19
+ def add_attachment(table_name, *attachment_names)
20
+ raise ArgumentError, "Please specify attachment name in your add_attachment call in your migration." if attachment_names.empty?
21
+
22
+ options = attachment_names.extract_options!
23
+
24
+ attachment_names.each do |attachment_name|
25
+ COLUMNS.each_pair do |column_name, column_type|
26
+ column_options = options.merge(options[column_name.to_sym] || {})
27
+ add_column(table_name, "#{attachment_name}_#{column_name}", column_type, column_options)
28
+ end
29
+ end
30
+ end
31
+
32
+ def remove_attachment(table_name, *attachment_names)
33
+ raise ArgumentError, "Please specify attachment name in your remove_attachment call in your migration." if attachment_names.empty?
34
+
35
+ attachment_names.each do |attachment_name|
36
+ COLUMNS.keys.each do |column_name|
37
+ remove_column(table_name, "#{attachment_name}_#{column_name}")
38
+ end
39
+ end
40
+ end
41
+
42
+ def drop_attached_file(*args)
43
+ ActiveSupport::Deprecation.warn "Method `drop_attached_file` in the migration has been deprecated and will be replaced by `remove_attachment`."
44
+ remove_attachment(*args)
45
+ end
46
+ end
47
+
48
+ module TableDefinition
49
+ def attachment(*attachment_names)
50
+ options = attachment_names.extract_options!
51
+ attachment_names.each do |attachment_name|
52
+ COLUMNS.each_pair do |column_name, column_type|
53
+ column_options = options.merge(options[column_name.to_sym] || {})
54
+ column("#{attachment_name}_#{column_name}", column_type, column_options)
55
+ end
56
+ end
57
+ end
58
+
59
+ def has_attached_file(*attachment_names)
60
+ ActiveSupport::Deprecation.warn "Method `t.has_attached_file` in the migration has been deprecated and will be replaced by `t.attachment`."
61
+ attachment(*attachment_names)
62
+ end
63
+ end
64
+
65
+ module CommandRecorder
66
+ def add_attachment(*args)
67
+ record(:add_attachment, args)
68
+ end
69
+
70
+ private
71
+
72
+ def invert_add_attachment(args)
73
+ [:remove_attachment, args]
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,4 @@
1
+ require "paperclip/storage/filesystem"
2
+ require "paperclip/storage/fog"
3
+ require "paperclip/storage/s3"
4
+ require "paperclip/storage/database"
@@ -0,0 +1,140 @@
1
+ module Paperclip
2
+ module Storage
3
+ module Database
4
+
5
+ def self.extended base
6
+ base.instance_eval do
7
+ if @options[:url] == '/system/:attachment/:id/:style/:filename'
8
+ @options[:url] = "/:class/:id/:attachment?style=:style"
9
+ end
10
+ end
11
+ Paperclip.interpolates(:relative_root) do |attachment, style|
12
+ begin
13
+ if ActionController::AbstractRequest.respond_to?(:relative_url_root)
14
+ relative_url_root = ActionController::AbstractRequest.relative_url_root
15
+ end
16
+ rescue NameError
17
+ end
18
+ if !relative_url_root && ActionController::Base.respond_to?(:relative_url_root)
19
+ relative_url_root = ActionController::Base.relative_url_root
20
+ end
21
+ relative_url_root
22
+ end
23
+ end
24
+
25
+ def instance_read_file(style)
26
+ puts "STYLE #########: " + style.to_s
27
+ column = column_for_style(style)
28
+ responds = instance.respond_to?(column)
29
+ cached = self.instance_variable_get("@_#{column}")
30
+ return cached if cached
31
+ # The blob attribute will not be present if select_without_file_columns_for was used
32
+ instance.reload :select => column if !instance.attribute_present?(column) && !instance.new_record?
33
+ instance.send(column) if responds
34
+ end
35
+
36
+ def instance_write_file(style, value)
37
+ setter = :"#{column_for_style(style)}="
38
+ responds = instance.respond_to?(setter)
39
+ self.instance_variable_set("@_#{setter.to_s.chop}", value)
40
+ instance.send(setter, value) if responds
41
+ end
42
+
43
+ def file_contents(style = default_style)
44
+ instance_read_file(style)
45
+ end
46
+ alias_method :data, :file_contents
47
+
48
+ def exists?(style = default_style)
49
+ !file_contents(style).nil?
50
+ end
51
+
52
+ # Returns representation of the data of the file assigned to the given
53
+ # style, in the format most representative of the current storage.
54
+ def to_file style = default_style
55
+
56
+ if @queued_for_write[style]
57
+ @queued_for_write[style]
58
+ elsif exists?(style)
59
+ tempfile = Tempfile.new instance_read(:file_name)
60
+ tempfile.write file_contents(style)
61
+ tempfile
62
+ else
63
+ nil
64
+ end
65
+ end
66
+ alias_method :to_io, :to_file
67
+
68
+ def path style = default_style
69
+ nil
70
+ end
71
+
72
+ def assign uploaded_file
73
+
74
+ # Assign standard metadata attributes and perform post processing as usual
75
+ super
76
+
77
+ # Save the file contents for all styles in ActiveRecord immediately (before save)
78
+ @queued_for_write.each do |style, file|
79
+ file.rewind
80
+ instance_write_file(style, file.read)
81
+ end
82
+
83
+ # If we are assigning another Paperclip attachment, then fixup the
84
+ # filename and content type; necessary since Tempfile is used in to_file
85
+ if uploaded_file.is_a?(Paperclip::Attachment)
86
+ instance_write(:file_name, uploaded_file.instance_read(:file_name))
87
+ instance_write(:content_type, uploaded_file.instance_read(:content_type))
88
+ end
89
+ end
90
+
91
+ def queue_existing_for_delete
92
+ [:original, *styles.keys].uniq.each do |style|
93
+ instance_write_file(style, nil)
94
+ end
95
+ instance_write(:file_name, nil)
96
+ instance_write(:content_type, nil)
97
+ instance_write(:file_size, nil)
98
+ instance_write(:updated_at, nil)
99
+ end
100
+
101
+ def flush_writes
102
+ after_flush_writes # allows attachment to clean up temp files
103
+ @queued_for_write = {}
104
+ end
105
+
106
+ def flush_deletes
107
+ @queued_for_delete = []
108
+ end
109
+
110
+ private
111
+
112
+ def column_for_style style
113
+ @options[:file_columns][style.to_sym]
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ module Paperclip
120
+ module Storage
121
+ module Database
122
+ module ControllerClassMethods
123
+ def self.included(base)
124
+ base.extend(self)
125
+ end
126
+ def downloads_files_for(model, attachment)
127
+ define_method("#{attachment.to_s.pluralize}") do
128
+ user_id = params[:id]
129
+ user_id ||= params[:user_id]
130
+ model_record = Object.const_get(model.to_s.camelize.to_sym).find(user_id)
131
+ style = params[:style] ? params[:style] : 'original'
132
+ send_data model_record.send(attachment).file_contents(style),
133
+ :filename => model_record.send("#{attachment}_file_name".to_sym),
134
+ :type => model_record.send("#{attachment}_content_type".to_sym)
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ 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,244 @@
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
+ return false unless file
174
+ local_file.write(file.body)
175
+ end
176
+ rescue ::Fog::Errors::Error => e
177
+ warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
178
+ false
179
+ end
180
+
181
+ private
182
+
183
+ def convert_time(time)
184
+ if time.is_a?(Fixnum)
185
+ time = Time.now + time
186
+ end
187
+ time
188
+ end
189
+
190
+ def dynamic_fog_host_for_style(style)
191
+ if @options[:fog_host].respond_to?(:call)
192
+ @options[:fog_host].call(self)
193
+ else
194
+ (@options[:fog_host] =~ /%d/) ? @options[:fog_host] % (path(style).hash % 4) : @options[:fog_host]
195
+ end
196
+ end
197
+
198
+ def host_name_for_directory
199
+ if directory_name.to_s =~ Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX
200
+ "#{directory_name}.s3.amazonaws.com"
201
+ else
202
+ "s3.amazonaws.com/#{directory_name}"
203
+ end
204
+ end
205
+
206
+ def find_credentials(creds)
207
+ case creds
208
+ when File
209
+ YAML::load(ERB.new(File.read(creds.path)).result)
210
+ when String, Pathname
211
+ YAML::load(ERB.new(File.read(creds)).result)
212
+ when Hash
213
+ creds
214
+ else
215
+ if creds.respond_to?(:call)
216
+ creds.call(self)
217
+ else
218
+ raise ArgumentError, "Credentials are not a path, file, hash or proc."
219
+ end
220
+ end
221
+ end
222
+
223
+ def connection
224
+ @connection ||= ::Fog::Storage.new(fog_credentials)
225
+ end
226
+
227
+ def directory
228
+ @directory ||= connection.directories.new(key: directory_name)
229
+ end
230
+
231
+ def directory_name
232
+ if @options[:fog_directory].respond_to?(:call)
233
+ @options[:fog_directory].call(self)
234
+ else
235
+ @options[:fog_directory]
236
+ end
237
+ end
238
+
239
+ def scheme
240
+ @scheme ||= fog_credentials[:scheme] || 'https'
241
+ end
242
+ end
243
+ end
244
+ end