paperclip 2.3.3 → 2.3.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of paperclip might be problematic. Click here for more details.

@@ -15,6 +15,8 @@ useful defaults.
15
15
  See the documentation for +has_attached_file+ in Paperclip::ClassMethods for
16
16
  more detailed options.
17
17
 
18
+ The complete RDoc[http://rdoc.info/projects/thoughtbot/paperclip] is online.
19
+
18
20
  ==Quick Start
19
21
 
20
22
  In your model:
@@ -173,7 +175,8 @@ If you'd like to contribute a feature or bugfix: Thanks! To make sure your
173
175
  fix/feature has a high chance of being included, please read the following
174
176
  guidelines:
175
177
 
176
- 1. Ask on the mailing list, or post a new GitHub Issue.
178
+ 1. Ask on the mailing list[http://groups.google.com/group/paperclip-plugin], or
179
+ post a new GitHub Issue[http://github.com/thoughtbot/paperclip/issues].
177
180
  2. Make sure there are tests! We will not accept any patch that is not tested.
178
181
  It's a rare time when explicit tests aren't needed. If you have questions
179
182
  about writing tests for paperclip, please ask the mailing list.
@@ -26,6 +26,7 @@
26
26
  # See the +has_attached_file+ documentation for more details.
27
27
 
28
28
  require 'erb'
29
+ require 'digest'
29
30
  require 'tempfile'
30
31
  require 'paperclip/version'
31
32
  require 'paperclip/upfile'
@@ -33,11 +34,12 @@ require 'paperclip/iostream'
33
34
  require 'paperclip/geometry'
34
35
  require 'paperclip/processor'
35
36
  require 'paperclip/thumbnail'
36
- require 'paperclip/storage'
37
37
  require 'paperclip/interpolations'
38
38
  require 'paperclip/style'
39
39
  require 'paperclip/attachment'
40
+ require 'paperclip/storage'
40
41
  require 'paperclip/callback_compatability'
42
+ require 'paperclip/command_line'
41
43
  require 'paperclip/railtie'
42
44
  if defined?(Rails.root) && Rails.root
43
45
  Dir.glob(File.join(File.expand_path(Rails.root), "lib", "paperclip_processors", "*.rb")).each do |processor|
@@ -74,14 +76,6 @@ module Paperclip
74
76
  yield(self) if block_given?
75
77
  end
76
78
 
77
- def path_for_command command #:nodoc:
78
- if options[:image_magick_path]
79
- warn("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
80
- end
81
- path = [options[:command_path] || options[:image_magick_path], command].compact
82
- File.join(*path)
83
- end
84
-
85
79
  def interpolates key, &block
86
80
  Paperclip::Interpolations[key] = block
87
81
  end
@@ -103,48 +97,11 @@ module Paperclip
103
97
  # Paperclip.options[:log_command] is set to true (defaults to false). This
104
98
  # will only log if logging in general is set to true as well.
105
99
  def run cmd, *params
106
- options = params.last.is_a?(Hash) ? params.pop : {}
107
- expected_outcodes = options[:expected_outcodes] || [0]
108
- params = quote_command_options(*params).join(" ")
109
-
110
- command = %Q[#{path_for_command(cmd)} #{params}]
111
- command = "#{command} 2>#{bit_bucket}" if Paperclip.options[:swallow_stderr]
112
- Paperclip.log(command) if Paperclip.options[:log_command]
113
-
114
- begin
115
- output = `#{command}`
116
-
117
- raise CommandNotFoundError if $?.exitstatus == 127
118
-
119
- unless expected_outcodes.include?($?.exitstatus)
120
- raise PaperclipCommandLineError,
121
- "Error while running #{cmd}. Expected return code to be #{expected_outcodes.join(", ")} but was #{$?.exitstatus}",
122
- output
123
- end
124
- rescue Errno::ENOENT => e
125
- raise CommandNotFoundError
126
- end
127
-
128
- output
129
- end
130
-
131
- def quote_command_options(*options)
132
- options.map do |option|
133
- option.split("'").map{|m| "'#{m}'" }.join("\\'")
134
- end
135
- end
136
-
137
- def bit_bucket #:nodoc:
138
- File.exists?("/dev/null") ? "/dev/null" : "NUL"
139
- end
140
-
141
- def included base #:nodoc:
142
- base.extend ClassMethods
143
- if base.respond_to?("set_callback")
144
- base.send :include, Paperclip::CallbackCompatability::Rails3
145
- else
146
- base.send :include, Paperclip::CallbackCompatability::Rails21
100
+ if options[:image_magick_path]
101
+ Paperclip.log("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
147
102
  end
103
+ CommandLine.path = options[:command_path] || options[:image_magick_path]
104
+ CommandLine.new(cmd, *params).run
148
105
  end
149
106
 
150
107
  def processor name #:nodoc:
@@ -182,6 +139,9 @@ module Paperclip
182
139
  end
183
140
  end
184
141
 
142
+ class StorageMethodNotFound < PaperclipError
143
+ end
144
+
185
145
  class CommandNotFoundError < PaperclipError
186
146
  end
187
147
 
@@ -191,6 +151,17 @@ module Paperclip
191
151
  class InfiniteInterpolationError < PaperclipError #:nodoc:
192
152
  end
193
153
 
154
+ module Glue
155
+ def self.included base #:nodoc:
156
+ base.extend ClassMethods
157
+ if base.respond_to?("set_callback")
158
+ base.send :include, Paperclip::CallbackCompatability::Rails3
159
+ else
160
+ base.send :include, Paperclip::CallbackCompatability::Rails21
161
+ end
162
+ end
163
+ end
164
+
194
165
  module ClassMethods
195
166
  # +has_attached_file+ gives the class it is called on an attribute that maps to a file. This
196
167
  # is typically a file stored somewhere on the filesystem and has been uploaded by a user.
@@ -302,10 +273,11 @@ module Paperclip
302
273
  message = message.gsub(/:min/, min.to_s).gsub(/:max/, max.to_s)
303
274
 
304
275
  validates_inclusion_of :"#{name}_file_size",
305
- :in => range,
306
- :message => message,
307
- :if => options[:if],
308
- :unless => options[:unless]
276
+ :in => range,
277
+ :message => message,
278
+ :if => options[:if],
279
+ :unless => options[:unless],
280
+ :allow_nil => true
309
281
  end
310
282
 
311
283
  # Adds errors if thumbnail creation fails. The same as specifying :whiny_thumbnails => true.
@@ -324,9 +296,9 @@ module Paperclip
324
296
  def validates_attachment_presence name, options = {}
325
297
  message = options[:message] || "must be set."
326
298
  validates_presence_of :"#{name}_file_name",
327
- :message => message,
328
- :if => options[:if],
329
- :unless => options[:unless]
299
+ :message => message,
300
+ :if => options[:if],
301
+ :unless => options[:unless]
330
302
  end
331
303
 
332
304
  # Places ActiveRecord-style validations on the content type of the file
@@ -346,11 +318,12 @@ module Paperclip
346
318
  # model, content_type validation will work _ONLY upon assignment_ and
347
319
  # re-validation after the instance has been reloaded will always succeed.
348
320
  def validates_attachment_content_type name, options = {}
349
- types = [options.delete(:content_type)].flatten
350
- validates_each(:"#{name}_content_type", options) do |record, attr, value|
351
- unless types.any?{|t| t === value }
321
+ validation_options = options.dup
322
+ allowed_types = [validation_options[:content_type]].flatten
323
+ validates_each(:"#{name}_content_type", validation_options) do |record, attr, value|
324
+ if !allowed_types.any?{|t| t === value } && !(value.nil? || value.blank?)
352
325
  if record.errors.method(:add).arity == -2
353
- message = options[:message] || "is not one of #{types.join(", ")}"
326
+ message = options[:message] || "is not one of #{allowed_types.join(", ")}"
354
327
  record.errors.add(:"#{name}_content_type", message)
355
328
  else
356
329
  record.errors.add(:"#{name}_content_type", :inclusion, :default => options[:message], :value => value)
@@ -15,6 +15,7 @@ module Paperclip
15
15
  :default_url => "/:attachment/:style/missing.png",
16
16
  :default_style => :original,
17
17
  :storage => :filesystem,
18
+ :use_timestamp => true,
18
19
  :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails]
19
20
  }
20
21
  end
@@ -39,6 +40,7 @@ module Paperclip
39
40
  @default_url = options[:default_url]
40
41
  @default_style = options[:default_style]
41
42
  @storage = options[:storage]
43
+ @use_timestamp = options[:use_timestamp]
42
44
  @whiny = options[:whiny_thumbnails] || options[:whiny]
43
45
  @convert_options = options[:convert_options]
44
46
  @processors = options[:processors]
@@ -55,7 +57,7 @@ module Paperclip
55
57
  unless @normalized_styles
56
58
  @normalized_styles = {}
57
59
  (@styles.respond_to?(:call) ? @styles.call(self) : @styles).each do |name, args|
58
- @normalized_styles[name] = Paperclip::Style.new(name, args, self)
60
+ @normalized_styles[name] = Paperclip::Style.new(name, args.dup, self)
59
61
  end
60
62
  end
61
63
  @normalized_styles
@@ -90,6 +92,7 @@ module Paperclip
90
92
  instance_write(:file_name, uploaded_file.original_filename.strip)
91
93
  instance_write(:content_type, uploaded_file.content_type.to_s.strip)
92
94
  instance_write(:file_size, uploaded_file.size.to_i)
95
+ instance_write(:fingerprint, uploaded_file.fingerprint)
93
96
  instance_write(:updated_at, Time.now)
94
97
 
95
98
  @dirty = true
@@ -97,7 +100,8 @@ module Paperclip
97
100
  post_process
98
101
 
99
102
  # Reset the file size if the original file was reprocessed.
100
- instance_write(:file_size, @queued_for_write[:original].size.to_i)
103
+ instance_write(:file_size, @queued_for_write[:original].size.to_i)
104
+ instance_write(:fingerprint, @queued_for_write[:original].fingerprint)
101
105
  ensure
102
106
  uploaded_file.close if close_uploaded_file
103
107
  end
@@ -106,12 +110,11 @@ module Paperclip
106
110
  # this does not necessarily need to point to a file that your web server
107
111
  # can access and can point to an action in your app, if you need fine
108
112
  # grained security. This is not recommended if you don't need the
109
- # security, however, for performance reasons. set
110
- # include_updated_timestamp to false if you want to stop the attachment
111
- # update time appended to the url
112
- def url style_name = default_style, include_updated_timestamp = true
113
+ # security, however, for performance reasons. Set use_timestamp to false
114
+ # if you want to stop the attachment update time appended to the url
115
+ def url(style_name = default_style, use_timestamp = @use_timestamp)
113
116
  url = original_filename.nil? ? interpolate(@default_url, style_name) : interpolate(@url, style_name)
114
- include_updated_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
117
+ use_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
115
118
  end
116
119
 
117
120
  # Returns the path of the attachment as defined by the :path option. If the
@@ -174,6 +177,12 @@ module Paperclip
174
177
  instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
175
178
  end
176
179
 
180
+ # Returns the hash of the file as originally assigned, and lives in the
181
+ # <attachment>_fingerprint attribute of the model.
182
+ def fingerprint
183
+ instance_read(:fingerprint) || (@queued_for_write[:original] && @queued_for_write[:original].fingerprint)
184
+ end
185
+
177
186
  # Returns the content_type of the file as originally assigned, and lives
178
187
  # in the <attachment>_content_type attribute of the model.
179
188
  def content_type
@@ -207,7 +216,7 @@ module Paperclip
207
216
  new_original = Tempfile.new("paperclip-reprocess")
208
217
  new_original.binmode
209
218
  if old_original = to_file(:original)
210
- new_original.write( old_original.read )
219
+ new_original.write( old_original.respond_to?(:get) ? old_original.get : old_original.read )
211
220
  new_original.rewind
212
221
 
213
222
  @queued_for_write = { :original => new_original }
@@ -265,7 +274,12 @@ module Paperclip
265
274
  end
266
275
 
267
276
  def initialize_storage #:nodoc:
268
- @storage_module = Paperclip::Storage.const_get(@storage.to_s.capitalize)
277
+ storage_class_name = @storage.to_s.capitalize
278
+ begin
279
+ @storage_module = Paperclip::Storage.const_get(storage_class_name)
280
+ rescue NameError
281
+ raise StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'"
282
+ end
269
283
  self.extend(@storage_module)
270
284
  end
271
285
 
@@ -0,0 +1,80 @@
1
+ module Paperclip
2
+ class CommandLine
3
+ class << self
4
+ attr_accessor :path
5
+ end
6
+
7
+ def initialize(binary, params = "", options = {})
8
+ @binary = binary.dup
9
+ @params = params.dup
10
+ @options = options.dup
11
+ @swallow_stderr = @options.has_key?(:swallow_stderr) ? @options.delete(:swallow_stderr) : Paperclip.options[:swallow_stderr]
12
+ @expected_outcodes = @options.delete(:expected_outcodes)
13
+ @expected_outcodes ||= [0]
14
+ end
15
+
16
+ def command
17
+ cmd = []
18
+ cmd << full_path(@binary)
19
+ cmd << interpolate(@params, @options)
20
+ cmd << bit_bucket if @swallow_stderr
21
+ cmd.join(" ")
22
+ end
23
+
24
+ def run
25
+ Paperclip.log(command)
26
+ begin
27
+ output = self.class.send(:'`', command)
28
+ rescue Errno::ENOENT
29
+ raise Paperclip::CommandNotFoundError
30
+ end
31
+ if $?.exitstatus == 127
32
+ raise Paperclip::CommandNotFoundError
33
+ end
34
+ unless @expected_outcodes.include?($?.exitstatus)
35
+ raise Paperclip::PaperclipCommandLineError, "Command '#{command}' returned #{$?.exitstatus}. Expected #{@expected_outcodes.join(", ")}"
36
+ end
37
+ output
38
+ end
39
+
40
+ private
41
+
42
+ def full_path(binary)
43
+ [self.class.path, binary].compact.join("/")
44
+ end
45
+
46
+ def interpolate(pattern, vars)
47
+ # interpolates :variables and :{variables}
48
+ pattern.gsub(%r#:(?:\w+|\{\w+\})#) do |match|
49
+ key = match[1..-1]
50
+ key = key[1..-2] if key[0,1] == '{'
51
+ if invalid_variables.include?(key)
52
+ raise PaperclipCommandLineError,
53
+ "Interpolation of #{key} isn't allowed."
54
+ end
55
+ shell_quote(vars[key.to_sym])
56
+ end
57
+ end
58
+
59
+ def invalid_variables
60
+ %w(expected_outcodes swallow_stderr)
61
+ end
62
+
63
+ def shell_quote(string)
64
+ return "" if string.nil? or string.blank?
65
+ if self.class.unix?
66
+ string.split("'").map{|m| "'#{m}'" }.join("\\'")
67
+ else
68
+ %{"#{string}"}
69
+ end
70
+ end
71
+
72
+ def bit_bucket
73
+ self.class.unix? ? "2>/dev/null" : "2>NUL"
74
+ end
75
+
76
+ def self.unix?
77
+ File.exist?("/dev/null")
78
+ end
79
+ end
80
+ end
@@ -16,7 +16,7 @@ module Paperclip
16
16
  def self.from_file file
17
17
  file = file.path if file.respond_to? "path"
18
18
  geometry = begin
19
- Paperclip.run("identify", "-format", "%wx%h", "#{file}[0]")
19
+ Paperclip.run("identify", "-format %wx%h :file", :file => "#{file}[0]")
20
20
  rescue PaperclipCommandLineError
21
21
  ""
22
22
  end
@@ -42,7 +42,7 @@ module Paperclip
42
42
  # contains ":url" to prevent infinite recursion. This interpolation
43
43
  # is used in the default :path to ease default specifications.
44
44
  def url attachment, style_name
45
- raise InfiniteInterpolationError if attachment.options[:url].include?(":url")
45
+ raise InfiniteInterpolationError if caller.any?{|b| b.index("#{__FILE__}:#{__LINE__ + 1}") }
46
46
  attachment.url(style_name, false)
47
47
  end
48
48
 
@@ -88,6 +88,11 @@ module Paperclip
88
88
  attachment.instance.id
89
89
  end
90
90
 
91
+ # Returns the fingerprint of the instance.
92
+ def fingerprint attachment, style_name
93
+ attachment.fingerprint
94
+ end
95
+
91
96
  # Returns the id of the instance in a split path form. e.g. returns
92
97
  # 000/001/234 for an id of 1234.
93
98
  def id_partition attachment, style_name
@@ -5,7 +5,7 @@ module IOStream
5
5
  # Returns a Tempfile containing the contents of the readable object.
6
6
  def to_tempfile
7
7
  name = respond_to?(:original_filename) ? original_filename : (respond_to?(:path) ? path : "stream")
8
- tempfile = Paperclip::Tempfile.new("stream" + File.extname(name))
8
+ tempfile = Paperclip::Tempfile.new(["stream", File.extname(name)])
9
9
  tempfile.binmode
10
10
  self.stream_to(tempfile)
11
11
  end
@@ -57,7 +57,8 @@ module Paperclip
57
57
  file = StringIO.new(".")
58
58
  file.content_type = type
59
59
  (subject = @subject.new).attachment_for(@attachment_name).assign(file)
60
- subject.valid? && subject.errors[:"#{@attachment_name}_content_type"].blank?
60
+ subject.valid?
61
+ subject.errors[:"#{@attachment_name}_content_type"].blank?
61
62
  end
62
63
  end
63
64
 
@@ -40,10 +40,19 @@ module Paperclip
40
40
  # on this blog post:
41
41
  # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
42
42
  class Tempfile < ::Tempfile
43
- # Replaces Tempfile's +make_tmpname+ with one that honors file extensions.
44
- def make_tmpname(basename, n)
45
- extension = File.extname(basename)
46
- sprintf("%s,%d,%d%s", File.basename(basename, extension), $$, n.to_i, extension)
43
+ # This is Ruby 1.8.7's implementation.
44
+ if RUBY_VERSION <= "1.8.6"
45
+ def make_tmpname(basename, n)
46
+ case basename
47
+ when Array
48
+ prefix, suffix = *basename
49
+ else
50
+ prefix, suffix = basename, ''
51
+ end
52
+
53
+ t = time.now.strftime("%y%m%d")
54
+ path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}#{suffix}"
55
+ end
47
56
  end
48
57
  end
49
58
  end
@@ -17,7 +17,7 @@ module Paperclip
17
17
 
18
18
  class Railtie
19
19
  def self.insert
20
- ActiveRecord::Base.send(:include, Paperclip)
20
+ ActiveRecord::Base.send(:include, Paperclip::Glue)
21
21
  File.send(:include, Paperclip::Upfile)
22
22
  end
23
23
  end
@@ -1,247 +1,2 @@
1
- module Paperclip
2
- module Storage
3
-
4
- # The default place to store attachments is in the filesystem. Files on the local
5
- # filesystem can be very easily served by Apache without requiring a hit to your app.
6
- # They also can be processed more easily after they've been saved, as they're just
7
- # normal files. There is one Filesystem-specific option for has_attached_file.
8
- # * +path+: The location of the repository of attachments on disk. This can (and, in
9
- # almost all cases, should) be coordinated with the value of the +url+ option to
10
- # allow files to be saved into a place where Apache can serve them without
11
- # hitting your app. Defaults to
12
- # ":rails_root/public/:attachment/:id/:style/:basename.:extension"
13
- # By default this places the files in the app's public directory which can be served
14
- # directly. If you are using capistrano for deployment, a good idea would be to
15
- # make a symlink to the capistrano-created system directory from inside your app's
16
- # public directory.
17
- # See Paperclip::Attachment#interpolate for more information on variable interpolaton.
18
- # :path => "/var/app/attachments/:class/:id/:style/:basename.:extension"
19
- module Filesystem
20
- def self.extended base
21
- end
22
-
23
- def exists?(style_name = default_style)
24
- if original_filename
25
- File.exist?(path(style_name))
26
- else
27
- false
28
- end
29
- end
30
-
31
- # Returns representation of the data of the file assigned to the given
32
- # style, in the format most representative of the current storage.
33
- def to_file style_name = default_style
34
- @queued_for_write[style_name] || (File.new(path(style_name), 'rb') if exists?(style_name))
35
- end
36
-
37
- def flush_writes #:nodoc:
38
- @queued_for_write.each do |style_name, file|
39
- file.close
40
- FileUtils.mkdir_p(File.dirname(path(style_name)))
41
- log("saving #{path(style_name)}")
42
- FileUtils.mv(file.path, path(style_name))
43
- FileUtils.chmod(0644, path(style_name))
44
- end
45
- @queued_for_write = {}
46
- end
47
-
48
- def flush_deletes #:nodoc:
49
- @queued_for_delete.each do |path|
50
- begin
51
- log("deleting #{path}")
52
- FileUtils.rm(path) if File.exist?(path)
53
- rescue Errno::ENOENT => e
54
- # ignore file-not-found, let everything else pass
55
- end
56
- begin
57
- while(true)
58
- path = File.dirname(path)
59
- FileUtils.rmdir(path)
60
- end
61
- rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR
62
- # Stop trying to remove parent directories
63
- rescue SystemCallError => e
64
- log("There was an unexpected error while deleting directories: #{e.class}")
65
- # Ignore it
66
- end
67
- end
68
- @queued_for_delete = []
69
- end
70
- end
71
-
72
- # Amazon's S3 file hosting service is a scalable, easy place to store files for
73
- # distribution. You can find out more about it at http://aws.amazon.com/s3
74
- # There are a few S3-specific options for has_attached_file:
75
- # * +s3_credentials+: Takes a path, a File, or a Hash. The path (or File) must point
76
- # to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon
77
- # gives you. You can 'environment-space' this just like you do to your
78
- # database.yml file, so different environments can use different accounts:
79
- # development:
80
- # access_key_id: 123...
81
- # secret_access_key: 123...
82
- # test:
83
- # access_key_id: abc...
84
- # secret_access_key: abc...
85
- # production:
86
- # access_key_id: 456...
87
- # secret_access_key: 456...
88
- # This is not required, however, and the file may simply look like this:
89
- # access_key_id: 456...
90
- # secret_access_key: 456...
91
- # In which case, those access keys will be used in all environments. You can also
92
- # put your bucket name in this file, instead of adding it to the code directly.
93
- # This is useful when you want the same account but a different bucket for
94
- # development versus production.
95
- # * +s3_permissions+: This is a String that should be one of the "canned" access
96
- # policies that S3 provides (more information can be found here:
97
- # http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html#RESTCannedAccessPolicies)
98
- # The default for Paperclip is :public_read.
99
- # * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
100
- # 'http' or 'https'. Defaults to 'http' when your :s3_permissions are :public_read (the
101
- # default), and 'https' when your :s3_permissions are anything else.
102
- # * +s3_headers+: A hash of headers such as {'Expires' => 1.year.from_now.httpdate}
103
- # * +bucket+: This is the name of the S3 bucket that will store your files. Remember
104
- # that the bucket must be unique across all of Amazon S3. If the bucket does not exist
105
- # Paperclip will attempt to create it. The bucket name will not be interpolated.
106
- # You can define the bucket as a Proc if you want to determine it's name at runtime.
107
- # Paperclip will call that Proc with attachment as the only argument.
108
- # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
109
- # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
110
- # link in the +url+ entry for more information about S3 domains and buckets.
111
- # * +url+: There are three options for the S3 url. You can choose to have the bucket's name
112
- # placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
113
- # Lastly, you can specify a CNAME (which requires the CNAME to be specified as
114
- # :s3_alias_url. You can read more about CNAMEs and S3 at
115
- # http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html
116
- # Normally, this won't matter in the slightest and you can leave the default (which is
117
- # path-style, or :s3_path_url). But in some cases paths don't work and you need to use
118
- # the domain-style (:s3_domain_url). Anything else here will be treated like path-style.
119
- # NOTE: If you use a CNAME for use with CloudFront, you can NOT specify https as your
120
- # :s3_protocol; This is *not supported* by S3/CloudFront. Finally, when using the host
121
- # alias, the :bucket parameter is ignored, as the hostname is used as the bucket name
122
- # by S3.
123
- # * +path+: This is the key under the bucket in which the file will be stored. The
124
- # URL will be constructed from the bucket and the path. This is what you will want
125
- # to interpolate. Keys should be unique, like filenames, and despite the fact that
126
- # S3 (strictly speaking) does not support directories, you can still use a / to
127
- # separate parts of your file name.
128
- module S3
129
- def self.extended base
130
- begin
131
- require 'aws/s3'
132
- rescue LoadError => e
133
- e.message << " (You may need to install the aws-s3 gem)"
134
- raise e
135
- end
136
-
137
- base.instance_eval do
138
- @s3_credentials = parse_credentials(@options[:s3_credentials])
139
- @bucket = @options[:bucket] || @s3_credentials[:bucket]
140
- @bucket = @bucket.call(self) if @bucket.is_a?(Proc)
141
- @s3_options = @options[:s3_options] || {}
142
- @s3_permissions = @options[:s3_permissions] || :public_read
143
- @s3_protocol = @options[:s3_protocol] || (@s3_permissions == :public_read ? 'http' : 'https')
144
- @s3_headers = @options[:s3_headers] || {}
145
- @s3_host_alias = @options[:s3_host_alias]
146
- @url = ":s3_path_url" unless @url.to_s.match(/^:s3.*url$/)
147
- AWS::S3::Base.establish_connection!( @s3_options.merge(
148
- :access_key_id => @s3_credentials[:access_key_id],
149
- :secret_access_key => @s3_credentials[:secret_access_key]
150
- ))
151
- end
152
- Paperclip.interpolates(:s3_alias_url) do |attachment, style|
153
- "#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
154
- end
155
- Paperclip.interpolates(:s3_path_url) do |attachment, style|
156
- "#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
157
- end
158
- Paperclip.interpolates(:s3_domain_url) do |attachment, style|
159
- "#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
160
- end
161
- end
162
-
163
- def expiring_url(time = 3600)
164
- AWS::S3::S3Object.url_for(path, bucket_name, :expires_in => time )
165
- end
166
-
167
- def bucket_name
168
- @bucket
169
- end
170
-
171
- def s3_host_alias
172
- @s3_host_alias
173
- end
174
-
175
- def parse_credentials creds
176
- creds = find_credentials(creds).stringify_keys
177
- (creds[Rails.env] || creds).symbolize_keys
178
- end
179
-
180
- def exists?(style = default_style)
181
- if original_filename
182
- AWS::S3::S3Object.exists?(path(style), bucket_name)
183
- else
184
- false
185
- end
186
- end
187
-
188
- def s3_protocol
189
- @s3_protocol
190
- end
191
-
192
- # Returns representation of the data of the file assigned to the given
193
- # style, in the format most representative of the current storage.
194
- def to_file style = default_style
195
- return @queued_for_write[style] if @queued_for_write[style]
196
- file = Tempfile.new(path(style))
197
- file.write(AWS::S3::S3Object.value(path(style), bucket_name))
198
- file.rewind
199
- return file
200
- end
201
-
202
- def flush_writes #:nodoc:
203
- @queued_for_write.each do |style, file|
204
- begin
205
- log("saving #{path(style)}")
206
- AWS::S3::S3Object.store(path(style),
207
- file,
208
- bucket_name,
209
- {:content_type => instance_read(:content_type),
210
- :access => @s3_permissions,
211
- }.merge(@s3_headers))
212
- rescue AWS::S3::ResponseError => e
213
- raise
214
- end
215
- end
216
- @queued_for_write = {}
217
- end
218
-
219
- def flush_deletes #:nodoc:
220
- @queued_for_delete.each do |path|
221
- begin
222
- log("deleting #{path}")
223
- AWS::S3::S3Object.delete(path, bucket_name)
224
- rescue AWS::S3::ResponseError
225
- # Ignore this.
226
- end
227
- end
228
- @queued_for_delete = []
229
- end
230
-
231
- def find_credentials creds
232
- case creds
233
- when File
234
- YAML::load(ERB.new(File.read(creds.path)).result)
235
- when String, Pathname
236
- YAML::load(ERB.new(File.read(creds)).result)
237
- when Hash
238
- creds
239
- else
240
- raise ArgumentError, "Credentials are not a path, file, or hash."
241
- end
242
- end
243
- private :find_credentials
244
-
245
- end
246
- end
247
- end
1
+ require "paperclip/storage/filesystem"
2
+ require "paperclip/storage/s3"