paperclip 2.3.11 → 2.3.16
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.
- data/README.md +30 -4
- data/Rakefile +3 -3
- data/init.rb +3 -0
- data/lib/generators/paperclip/paperclip_generator.rb +3 -1
- data/lib/paperclip/attachment.rb +27 -12
- data/lib/paperclip/{callback_compatability.rb → callback_compatibility.rb} +0 -0
- data/lib/paperclip/geometry.rb +4 -1
- data/lib/paperclip/interpolations.rb +8 -3
- data/lib/paperclip/iostream.rb +1 -1
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +20 -14
- data/lib/paperclip/processor.rb +1 -1
- data/lib/paperclip/storage/filesystem.rb +8 -3
- data/lib/paperclip/storage/fog.rb +48 -13
- data/lib/paperclip/storage/s3.rb +54 -16
- data/lib/paperclip/style.rb +4 -3
- data/lib/paperclip/thumbnail.rb +18 -3
- data/lib/paperclip/upfile.rb +20 -13
- data/lib/paperclip/version.rb +1 -1
- data/lib/paperclip.rb +60 -27
- data/lib/tasks/paperclip.rake +32 -23
- data/test/attachment_test.rb +116 -0
- data/test/fixtures/animated.gif +0 -0
- data/test/fog_test.rb +30 -19
- data/test/geometry_test.rb +29 -0
- data/test/helper.rb +3 -0
- data/test/integration_test.rb +40 -4
- data/test/interpolations_test.rb +14 -0
- data/test/matchers/validate_attachment_content_type_matcher_test.rb +42 -2
- data/test/paperclip_test.rb +18 -40
- data/test/storage_test.rb +244 -7
- data/test/style_test.rb +30 -5
- data/test/thumbnail_test.rb +111 -6
- data/test/upfile_test.rb +3 -2
- metadata +103 -122
- data/lib/paperclip/command_line.rb +0 -86
- data/test/command_line_test.rb +0 -138
data/README.md
CHANGED
@@ -36,13 +36,23 @@ In development mode, you might add this line to `config/environments/development
|
|
36
36
|
Installation
|
37
37
|
------------
|
38
38
|
|
39
|
+
Paperclip is distributed as a gem, which is how it should be used in your app. It's
|
40
|
+
technically still installable as a plugin, but that's discouraged, as Rails plays
|
41
|
+
well with gems.
|
42
|
+
|
39
43
|
Include the gem in your Gemfile:
|
40
44
|
|
41
45
|
gem "paperclip", "~> 2.3"
|
42
46
|
|
43
|
-
Or
|
47
|
+
Or, if you don't use Bundler (though you probably should, even in Rails 2), with config.gem
|
44
48
|
|
45
|
-
|
49
|
+
# In config/environment.rb
|
50
|
+
...
|
51
|
+
Rails::Initializer.run do |config|
|
52
|
+
...
|
53
|
+
config.gem "paperclip", :version => "~> 2.3"
|
54
|
+
...
|
55
|
+
end
|
46
56
|
|
47
57
|
Quick Start
|
48
58
|
-----------
|
@@ -95,7 +105,7 @@ Usage
|
|
95
105
|
The basics of paperclip are quite simple: Declare that your model has an
|
96
106
|
attachment with the has_attached_file method, and give it a name. Paperclip
|
97
107
|
will wrap up up to four attributes (all prefixed with that attachment's name,
|
98
|
-
so you can have multiple attachments per model if you wish) and give
|
108
|
+
so you can have multiple attachments per model if you wish) and give them a
|
99
109
|
friendly front end. The attributes are `<attachment>_file_name`,
|
100
110
|
`<attachment>_file_size`, `<attachment>_content_type`, and `<attachment>_updated_at`.
|
101
111
|
Only `<attachment>_file_name` is required for paperclip to operate. More
|
@@ -178,6 +188,12 @@ or more or the processors, and they are expected to ignore it.
|
|
178
188
|
_NOTE: Because processors operate by turning the original attachment into the
|
179
189
|
styles, no processors will be run if there are no styles defined._
|
180
190
|
|
191
|
+
If you're interested in caching your thumbnail's width, height and size in the
|
192
|
+
database, take a look at the [paperclip-meta](https://github.com/y8/paperclip-meta) gem.
|
193
|
+
|
194
|
+
Also, if you're interesting to generate the thumbnail on-the-fly, you might want
|
195
|
+
to look into the [attachment_on_the_fly](https://github.com/drpentode/Attachment-on-the-Fly) gem.
|
196
|
+
|
181
197
|
Events
|
182
198
|
------
|
183
199
|
|
@@ -196,11 +212,19 @@ _NOTE: Post processing will not even *start* if the attachment is not valid
|
|
196
212
|
according to the validations. Your callbacks and processors will *only* be
|
197
213
|
called with valid attachments._
|
198
214
|
|
215
|
+
URI Obfuscation
|
216
|
+
---------------
|
217
|
+
|
218
|
+
Paperclip has an interpolation called `:hash` for obfuscating filenames of publicly-available files. For more on this feature read author's own explanation.
|
219
|
+
|
220
|
+
[https://github.com/thoughtbot/paperclip/pull/416](https://github.com/thoughtbot/paperclip/pull/416)
|
221
|
+
|
199
222
|
Testing
|
200
223
|
-------
|
201
224
|
|
202
225
|
Paperclip provides rspec-compatible matchers for testing attachments. See the
|
203
|
-
documentation on Paperclip::Shoulda::Matchers
|
226
|
+
documentation on [Paperclip::Shoulda::Matchers](http://rubydoc.info/gems/paperclip/Paperclip/Shoulda/Matchers)
|
227
|
+
for more information.
|
204
228
|
|
205
229
|
Contributing
|
206
230
|
------------
|
@@ -215,6 +239,8 @@ guidelines:
|
|
215
239
|
It's a rare time when explicit tests aren't needed. If you have questions
|
216
240
|
about writing tests for paperclip, please ask the mailing list.
|
217
241
|
|
242
|
+
Please see CONTRIBUTING.md for details.
|
243
|
+
|
218
244
|
Credits
|
219
245
|
-------
|
220
246
|
|
data/Rakefile
CHANGED
@@ -4,13 +4,13 @@ require 'bundler/setup'
|
|
4
4
|
|
5
5
|
require 'rake'
|
6
6
|
require 'rake/testtask'
|
7
|
-
require '
|
7
|
+
require 'rdoc/task'
|
8
8
|
|
9
9
|
$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
|
10
10
|
require 'paperclip'
|
11
11
|
|
12
12
|
desc 'Default: run unit tests.'
|
13
|
-
task :default => [:clean, :all]
|
13
|
+
task :default => [:clean, 'appraisal:install', :all]
|
14
14
|
|
15
15
|
desc 'Test the paperclip plugin under all supported Rails versions.'
|
16
16
|
task :all do |t|
|
@@ -31,7 +31,7 @@ task :shell do |t|
|
|
31
31
|
end
|
32
32
|
|
33
33
|
desc 'Generate documentation for the paperclip plugin.'
|
34
|
-
|
34
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
35
35
|
rdoc.rdoc_dir = 'doc'
|
36
36
|
rdoc.title = 'Paperclip'
|
37
37
|
rdoc.options << '--line-numbers' << '--inline-source'
|
data/init.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'rails/generators/active_record'
|
2
2
|
|
3
3
|
class PaperclipGenerator < ActiveRecord::Generators::Base
|
4
|
-
desc "Create a migration to add paperclip-specific fields to your model."
|
4
|
+
desc "Create a migration to add paperclip-specific fields to your model. " +
|
5
|
+
"The NAME argument is the name of your model, and the following " +
|
6
|
+
"arguments are the name of the attachments"
|
5
7
|
|
6
8
|
argument :attachment_names, :required => true, :type => :array, :desc => "The names of the attachment(s) to add.",
|
7
9
|
:banner => "attachment_one attachment_two attachment_three ..."
|
data/lib/paperclip/attachment.rb
CHANGED
@@ -11,6 +11,7 @@ module Paperclip
|
|
11
11
|
:url => "/system/:attachment/:id/:style/:filename",
|
12
12
|
:path => ":rails_root/public:url",
|
13
13
|
:styles => {},
|
14
|
+
:only_process => [],
|
14
15
|
:processors => [:thumbnail],
|
15
16
|
:convert_options => {},
|
16
17
|
:default_url => "/:attachment/:style/missing.png",
|
@@ -20,7 +21,8 @@ module Paperclip
|
|
20
21
|
:whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
|
21
22
|
:use_default_time_zone => true,
|
22
23
|
:hash_digest => "SHA1",
|
23
|
-
:hash_data => ":class/:attachment/:id/:style/:updated_at"
|
24
|
+
:hash_data => ":class/:attachment/:id/:style/:updated_at",
|
25
|
+
:preserve_files => false
|
24
26
|
}
|
25
27
|
end
|
26
28
|
|
@@ -41,6 +43,7 @@ module Paperclip
|
|
41
43
|
@path = options[:path]
|
42
44
|
@path = @path.call(self) if @path.is_a?(Proc)
|
43
45
|
@styles = options[:styles]
|
46
|
+
@only_process = options[:only_process]
|
44
47
|
@normalized_styles = nil
|
45
48
|
@default_url = options[:default_url]
|
46
49
|
@default_style = options[:default_style]
|
@@ -53,6 +56,7 @@ module Paperclip
|
|
53
56
|
@hash_secret = options[:hash_secret]
|
54
57
|
@convert_options = options[:convert_options]
|
55
58
|
@processors = options[:processors]
|
59
|
+
@preserve_files = options[:preserve_files]
|
56
60
|
@options = options
|
57
61
|
@post_processing = true
|
58
62
|
@queued_for_delete = []
|
@@ -64,8 +68,8 @@ module Paperclip
|
|
64
68
|
end
|
65
69
|
|
66
70
|
def styles
|
67
|
-
|
68
|
-
@normalized_styles =
|
71
|
+
if @styles.respond_to?(:call) || !@normalized_styles
|
72
|
+
@normalized_styles = ActiveSupport::OrderedHash.new
|
69
73
|
(@styles.respond_to?(:call) ? @styles.call(self) : @styles).each do |name, args|
|
70
74
|
@normalized_styles[name] = Paperclip::Style.new(name, args.dup, self)
|
71
75
|
end
|
@@ -107,7 +111,7 @@ module Paperclip
|
|
107
111
|
|
108
112
|
@dirty = true
|
109
113
|
|
110
|
-
post_process if @post_processing
|
114
|
+
post_process(*@only_process) if @post_processing
|
111
115
|
|
112
116
|
# Reset the file size if the original file was reprocessed.
|
113
117
|
instance_write(:file_size, @queued_for_write[:original].size.to_i)
|
@@ -123,7 +127,8 @@ module Paperclip
|
|
123
127
|
# security, however, for performance reasons. Set use_timestamp to false
|
124
128
|
# if you want to stop the attachment update time appended to the url
|
125
129
|
def url(style_name = default_style, use_timestamp = @use_timestamp)
|
126
|
-
|
130
|
+
default_url = @default_url.is_a?(Proc) ? @default_url.call(self) : @default_url
|
131
|
+
url = original_filename.nil? ? interpolate(default_url, style_name) : interpolate(@url, style_name)
|
127
132
|
use_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
|
128
133
|
end
|
129
134
|
|
@@ -164,6 +169,7 @@ module Paperclip
|
|
164
169
|
# use #destroy.
|
165
170
|
def clear
|
166
171
|
queue_existing_for_delete
|
172
|
+
@queued_for_write = {}
|
167
173
|
@errors = {}
|
168
174
|
end
|
169
175
|
|
@@ -171,8 +177,10 @@ module Paperclip
|
|
171
177
|
# nil to the attachment *and saving*. This is permanent. If you wish to
|
172
178
|
# wipe out the existing attachment but not save, use #clear.
|
173
179
|
def destroy
|
174
|
-
|
175
|
-
|
180
|
+
unless @preserve_files
|
181
|
+
clear
|
182
|
+
save
|
183
|
+
end
|
176
184
|
end
|
177
185
|
|
178
186
|
# Returns the name of the file as originally assigned, and lives in the
|
@@ -222,9 +230,13 @@ module Paperclip
|
|
222
230
|
end
|
223
231
|
|
224
232
|
def generate_fingerprint(source)
|
225
|
-
|
226
|
-
|
227
|
-
|
233
|
+
if source.respond_to?(:path) && source.path && !source.path.blank?
|
234
|
+
Digest::MD5.file(source.path).to_s
|
235
|
+
else
|
236
|
+
data = source.read
|
237
|
+
source.rewind if source.respond_to?(:rewind)
|
238
|
+
Digest::MD5.hexdigest(data)
|
239
|
+
end
|
228
240
|
end
|
229
241
|
|
230
242
|
# Paths and URLs can have a number of variables interpolated into them
|
@@ -251,6 +263,7 @@ module Paperclip
|
|
251
263
|
new_original.rewind
|
252
264
|
|
253
265
|
@queued_for_write = { :original => new_original }
|
266
|
+
instance_write(:updated_at, Time.now)
|
254
267
|
post_process(*style_args)
|
255
268
|
|
256
269
|
old_original.close if old_original.respond_to?(:close)
|
@@ -269,6 +282,8 @@ module Paperclip
|
|
269
282
|
!original_filename.blank?
|
270
283
|
end
|
271
284
|
|
285
|
+
alias :present? :file?
|
286
|
+
|
272
287
|
# Writes the attachment-specific attribute on the instance. For example,
|
273
288
|
# instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
|
274
289
|
# "avatar_file_name" field (assuming the attachment is called avatar).
|
@@ -308,7 +323,7 @@ module Paperclip
|
|
308
323
|
end
|
309
324
|
|
310
325
|
def initialize_storage #:nodoc:
|
311
|
-
storage_class_name = @storage.to_s.
|
326
|
+
storage_class_name = @storage.to_s.downcase.camelize
|
312
327
|
begin
|
313
328
|
@storage_module = Paperclip::Storage.const_get(storage_class_name)
|
314
329
|
rescue NameError
|
@@ -356,7 +371,7 @@ module Paperclip
|
|
356
371
|
end
|
357
372
|
|
358
373
|
def queue_existing_for_delete #:nodoc:
|
359
|
-
return unless file?
|
374
|
+
return unless (file? && @preserve_files==false)
|
360
375
|
@queued_for_delete += [:original, *styles.keys].uniq.map do |style|
|
361
376
|
path(style) if exists?(style)
|
362
377
|
end.compact
|
File without changes
|
data/lib/paperclip/geometry.rb
CHANGED
@@ -15,10 +15,13 @@ module Paperclip
|
|
15
15
|
# File or path.
|
16
16
|
def self.from_file file
|
17
17
|
file = file.path if file.respond_to? "path"
|
18
|
+
raise(Paperclip::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")) if file.blank?
|
18
19
|
geometry = begin
|
19
20
|
Paperclip.run("identify", "-format %wx%h :file", :file => "#{file}[0]")
|
20
|
-
rescue
|
21
|
+
rescue Cocaine::ExitStatusError
|
21
22
|
""
|
23
|
+
rescue Cocaine::CommandNotFoundError => e
|
24
|
+
raise Paperclip::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
|
22
25
|
end
|
23
26
|
parse(geometry) ||
|
24
27
|
raise(NotIdentifiedByImageMagickError.new("#{file} is not recognized by the 'identify' command."))
|
@@ -6,13 +6,13 @@ module Paperclip
|
|
6
6
|
module Interpolations
|
7
7
|
extend self
|
8
8
|
|
9
|
-
# Hash assignment of interpolations. Included only for
|
9
|
+
# Hash assignment of interpolations. Included only for compatibility,
|
10
10
|
# and is not intended for normal use.
|
11
11
|
def self.[]= name, block
|
12
12
|
define_method(name, &block)
|
13
13
|
end
|
14
14
|
|
15
|
-
# Hash access of interpolations. Included only for
|
15
|
+
# Hash access of interpolations. Included only for compatibility,
|
16
16
|
# and is not intended for normal use.
|
17
17
|
def self.[] name
|
18
18
|
method(name)
|
@@ -35,7 +35,7 @@ module Paperclip
|
|
35
35
|
|
36
36
|
# Returns the filename, the same way as ":basename.:extension" would.
|
37
37
|
def filename attachment, style_name
|
38
|
-
|
38
|
+
[ basename(attachment, style_name), extension(attachment, style_name) ].reject(&:blank?).join(".")
|
39
39
|
end
|
40
40
|
|
41
41
|
# Returns the interpolated URL. Will raise an error if the url itself
|
@@ -99,6 +99,11 @@ module Paperclip
|
|
99
99
|
attachment.instance.id
|
100
100
|
end
|
101
101
|
|
102
|
+
# Returns the #to_param of the instance.
|
103
|
+
def param attachment, style_name
|
104
|
+
attachment.instance.to_param
|
105
|
+
end
|
106
|
+
|
102
107
|
# Returns the fingerprint of the instance.
|
103
108
|
def fingerprint attachment, style_name
|
104
109
|
attachment.fingerprint
|
data/lib/paperclip/iostream.rb
CHANGED
@@ -17,6 +17,8 @@ module Paperclip
|
|
17
17
|
class ValidateAttachmentContentTypeMatcher
|
18
18
|
def initialize attachment_name
|
19
19
|
@attachment_name = attachment_name
|
20
|
+
@allowed_types = []
|
21
|
+
@rejected_types = []
|
20
22
|
end
|
21
23
|
|
22
24
|
def allowing *types
|
@@ -37,13 +39,19 @@ module Paperclip
|
|
37
39
|
end
|
38
40
|
|
39
41
|
def failure_message
|
40
|
-
"
|
41
|
-
|
42
|
+
"".tap do |str|
|
43
|
+
str << "Content types #{@allowed_types.join(", ")} should be accepted" if @allowed_types.present?
|
44
|
+
str << "\n" if @allowed_types.present && @rejected_types.present?
|
45
|
+
str << "Content types #{@rejected_types.join(", ")} should be rejected by #{@attachment_name}" if @rejected_types.present?
|
46
|
+
end
|
42
47
|
end
|
43
48
|
|
44
49
|
def negative_failure_message
|
45
|
-
"
|
46
|
-
|
50
|
+
"".tap do |str|
|
51
|
+
str << "Content types #{@allowed_types.join(", ")} should be rejected" if @allowed_types.present?
|
52
|
+
str << "\n" if @allowed_types.present && @rejected_types.present?
|
53
|
+
str << "Content types #{@rejected_types.join(", ")} should be accepted by #{@attachment_name}" if @rejected_types.present?
|
54
|
+
end
|
47
55
|
end
|
48
56
|
|
49
57
|
def description
|
@@ -52,22 +60,20 @@ module Paperclip
|
|
52
60
|
|
53
61
|
protected
|
54
62
|
|
55
|
-
def
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
subject.errors[:"#{@attachment_name}_content_type"].blank?
|
62
|
-
end
|
63
|
+
def type_allowed?(type)
|
64
|
+
file = StringIO.new(".")
|
65
|
+
file.content_type = type
|
66
|
+
(subject = @subject.new).attachment_for(@attachment_name).assign(file)
|
67
|
+
subject.valid?
|
68
|
+
subject.errors[:"#{@attachment_name}_content_type"].blank?
|
63
69
|
end
|
64
70
|
|
65
71
|
def allowed_types_allowed?
|
66
|
-
|
72
|
+
@allowed_types.all? { |type| type_allowed?(type) }
|
67
73
|
end
|
68
74
|
|
69
75
|
def rejected_types_rejected?
|
70
|
-
|
76
|
+
!@rejected_types.any? { |type| type_allowed?(type) }
|
71
77
|
end
|
72
78
|
end
|
73
79
|
end
|
data/lib/paperclip/processor.rb
CHANGED
@@ -41,7 +41,7 @@ module Paperclip
|
|
41
41
|
# http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
|
42
42
|
class Tempfile < ::Tempfile
|
43
43
|
# This is Ruby 1.8.7's implementation.
|
44
|
-
if RUBY_VERSION <= "1.8.6"
|
44
|
+
if RUBY_VERSION <= "1.8.6" || RUBY_PLATFORM =~ /java/
|
45
45
|
def make_tmpname(basename, n)
|
46
46
|
case basename
|
47
47
|
when Array
|
@@ -38,8 +38,13 @@ module Paperclip
|
|
38
38
|
file.close
|
39
39
|
FileUtils.mkdir_p(File.dirname(path(style_name)))
|
40
40
|
log("saving #{path(style_name)}")
|
41
|
-
|
42
|
-
|
41
|
+
begin
|
42
|
+
FileUtils.mv(file.path, path(style_name))
|
43
|
+
rescue SystemCallError
|
44
|
+
FileUtils.cp(file.path, path(style_name))
|
45
|
+
FileUtils.rm(file.path)
|
46
|
+
end
|
47
|
+
FileUtils.chmod(0666&~File.umask, path(style_name))
|
43
48
|
end
|
44
49
|
@queued_for_write = {}
|
45
50
|
end
|
@@ -58,7 +63,7 @@ module Paperclip
|
|
58
63
|
FileUtils.rmdir(path)
|
59
64
|
break if File.exists?(path) # Ruby 1.9.2 does not raise if the removal failed.
|
60
65
|
end
|
61
|
-
rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR
|
66
|
+
rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR, Errno::EACCES
|
62
67
|
# Stop trying to remove parent directories
|
63
68
|
rescue SystemCallError => e
|
64
69
|
log("There was an unexpected error while deleting directories: #{e.class}")
|
@@ -1,5 +1,35 @@
|
|
1
1
|
module Paperclip
|
2
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
|
+
# * +fog_directory+: This is the name of the S3 bucket that will
|
18
|
+
# store your files. Remember that the bucket must be unique across
|
19
|
+
# all of Amazon S3. If the bucket does not exist, Paperclip will
|
20
|
+
# attempt to create it.
|
21
|
+
# * +path+: This is the key under the bucket in which the file will
|
22
|
+
# be stored. The URL will be constructed from the bucket and the
|
23
|
+
# path. This is what you will want to interpolate. Keys should be
|
24
|
+
# unique, like filenames, and despite the fact that S3 (strictly
|
25
|
+
# speaking) does not support directories, you can still use a / to
|
26
|
+
# separate parts of your file name.
|
27
|
+
# * +fog_public+: (optional, defaults to true) Should the uploaded
|
28
|
+
# files be public or not? (true/false)
|
29
|
+
# * +fog_host+: (optional) The fully-qualified domain name (FQDN)
|
30
|
+
# that is the alias to the S3 domain of your bucket, e.g.
|
31
|
+
# 'http://images.example.com'. This can also be used in
|
32
|
+
# conjunction with Cloudfront (http://aws.amazon.com/cloudfront)
|
3
33
|
|
4
34
|
module Fog
|
5
35
|
def self.extended base
|
@@ -14,7 +44,8 @@ module Paperclip
|
|
14
44
|
@fog_directory = @options[:fog_directory]
|
15
45
|
@fog_credentials = @options[:fog_credentials]
|
16
46
|
@fog_host = @options[:fog_host]
|
17
|
-
@fog_public = @options[:fog_public]
|
47
|
+
@fog_public = @options[:fog_public] || true
|
48
|
+
@fog_file = @options[:fog_file] || {}
|
18
49
|
|
19
50
|
@url = ':fog_public_url'
|
20
51
|
Paperclip.interpolates(:fog_public_url) do |attachment, style|
|
@@ -34,11 +65,19 @@ module Paperclip
|
|
34
65
|
def flush_writes
|
35
66
|
for style, file in @queued_for_write do
|
36
67
|
log("saving #{path(style)}")
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
68
|
+
retried = false
|
69
|
+
begin
|
70
|
+
directory.files.create(@fog_file.merge(
|
71
|
+
:body => file,
|
72
|
+
:key => path(style),
|
73
|
+
:public => @fog_public
|
74
|
+
))
|
75
|
+
rescue Excon::Errors::NotFound
|
76
|
+
raise if retried
|
77
|
+
retried = true
|
78
|
+
directory.save
|
79
|
+
retry
|
80
|
+
end
|
42
81
|
end
|
43
82
|
@queued_for_write = {}
|
44
83
|
end
|
@@ -71,7 +110,8 @@ module Paperclip
|
|
71
110
|
|
72
111
|
def public_url(style = default_style)
|
73
112
|
if @fog_host
|
74
|
-
|
113
|
+
host = (@fog_host =~ /%d/) ? @fog_host % (path(style).hash % 4) : @fog_host
|
114
|
+
"#{host}/#{path(style)}"
|
75
115
|
else
|
76
116
|
directory.files.new(:key => path(style)).public_url
|
77
117
|
end
|
@@ -84,12 +124,7 @@ module Paperclip
|
|
84
124
|
end
|
85
125
|
|
86
126
|
def directory
|
87
|
-
@directory ||=
|
88
|
-
connection.directories.get(@fog_directory) || connection.directories.create(
|
89
|
-
:key => @fog_directory,
|
90
|
-
:public => @fog_public
|
91
|
-
)
|
92
|
-
end
|
127
|
+
@directory ||= connection.directories.new(:key => @fog_directory)
|
93
128
|
end
|
94
129
|
|
95
130
|
end
|
data/lib/paperclip/storage/s3.rb
CHANGED
@@ -27,6 +27,14 @@ module Paperclip
|
|
27
27
|
# policies that S3 provides (more information can be found here:
|
28
28
|
# http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAccessPolicy.html)
|
29
29
|
# The default for Paperclip is :public_read.
|
30
|
+
#
|
31
|
+
# You can set permission on a per style bases by doing the following:
|
32
|
+
# :s3_permissions => {
|
33
|
+
# :original => :private
|
34
|
+
# }
|
35
|
+
# Or globaly:
|
36
|
+
# :s3_permissions => :private
|
37
|
+
#
|
30
38
|
# * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
|
31
39
|
# 'http' or 'https'. Defaults to 'http' when your :s3_permissions are :public_read (the
|
32
40
|
# default), and 'https' when your :s3_permissions are anything else.
|
@@ -39,9 +47,9 @@ module Paperclip
|
|
39
47
|
# * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
|
40
48
|
# S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
|
41
49
|
# link in the +url+ entry for more information about S3 domains and buckets.
|
42
|
-
# * +url+: There are
|
50
|
+
# * +url+: There are four options for the S3 url. You can choose to have the bucket's name
|
43
51
|
# placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
|
44
|
-
#
|
52
|
+
# You can also specify a CNAME (which requires the CNAME to be specified as
|
45
53
|
# :s3_alias_url. You can read more about CNAMEs and S3 at
|
46
54
|
# http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html
|
47
55
|
# Normally, this won't matter in the slightest and you can leave the default (which is
|
@@ -50,12 +58,15 @@ module Paperclip
|
|
50
58
|
# NOTE: If you use a CNAME for use with CloudFront, you can NOT specify https as your
|
51
59
|
# :s3_protocol; This is *not supported* by S3/CloudFront. Finally, when using the host
|
52
60
|
# alias, the :bucket parameter is ignored, as the hostname is used as the bucket name
|
53
|
-
# by S3.
|
61
|
+
# by S3. The fourth option for the S3 url is :asset_host, which uses Rails' built-in
|
62
|
+
# asset_host settings. NOTE: To get the full url from a paperclip'd object, use the
|
63
|
+
# image_path helper; this is what image_tag uses to generate the url for an img tag.
|
54
64
|
# * +path+: This is the key under the bucket in which the file will be stored. The
|
55
65
|
# URL will be constructed from the bucket and the path. This is what you will want
|
56
66
|
# to interpolate. Keys should be unique, like filenames, and despite the fact that
|
57
67
|
# S3 (strictly speaking) does not support directories, you can still use a / to
|
58
68
|
# separate parts of your file name.
|
69
|
+
# * +s3_host_name+: If you are using your bucket in Tokyo region etc, write host_name.
|
59
70
|
module S3
|
60
71
|
def self.extended base
|
61
72
|
begin
|
@@ -67,48 +78,71 @@ module Paperclip
|
|
67
78
|
|
68
79
|
base.instance_eval do
|
69
80
|
@s3_credentials = parse_credentials(@options[:s3_credentials])
|
81
|
+
@s3_host_name = @options[:s3_host_name] || @s3_credentials[:s3_host_name]
|
70
82
|
@bucket = @options[:bucket] || @s3_credentials[:bucket]
|
71
83
|
@bucket = @bucket.call(self) if @bucket.is_a?(Proc)
|
72
84
|
@s3_options = @options[:s3_options] || {}
|
73
|
-
@s3_permissions = @options[:s3_permissions]
|
74
|
-
@s3_protocol = @options[:s3_protocol] ||
|
85
|
+
@s3_permissions = set_permissions(@options[:s3_permissions])
|
86
|
+
@s3_protocol = @options[:s3_protocol] ||
|
87
|
+
Proc.new do |style|
|
88
|
+
(@s3_permissions[style.to_sym] || @s3_permissions[:default]) == :public_read ? 'http' : 'https'
|
89
|
+
end
|
75
90
|
@s3_headers = @options[:s3_headers] || {}
|
76
91
|
@s3_host_alias = @options[:s3_host_alias]
|
92
|
+
@s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.is_a?(Proc)
|
77
93
|
unless @url.to_s.match(/^:s3.*url$/)
|
78
|
-
@path
|
79
|
-
@url
|
94
|
+
@path = @path.gsub(/:url/, @url)
|
95
|
+
@url = ":s3_path_url"
|
80
96
|
end
|
97
|
+
@url = ":asset_host" if @options[:url].to_s == ":asset_host"
|
81
98
|
AWS::S3::Base.establish_connection!( @s3_options.merge(
|
82
99
|
:access_key_id => @s3_credentials[:access_key_id],
|
83
100
|
:secret_access_key => @s3_credentials[:secret_access_key]
|
84
101
|
))
|
85
102
|
end
|
86
103
|
Paperclip.interpolates(:s3_alias_url) do |attachment, style|
|
87
|
-
"#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
|
104
|
+
"#{attachment.s3_protocol(style)}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
|
88
105
|
end unless Paperclip::Interpolations.respond_to? :s3_alias_url
|
89
106
|
Paperclip.interpolates(:s3_path_url) do |attachment, style|
|
90
|
-
"#{attachment.s3_protocol}
|
107
|
+
"#{attachment.s3_protocol(style)}://#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
|
91
108
|
end unless Paperclip::Interpolations.respond_to? :s3_path_url
|
92
109
|
Paperclip.interpolates(:s3_domain_url) do |attachment, style|
|
93
|
-
"#{attachment.s3_protocol}://#{attachment.bucket_name}.
|
110
|
+
"#{attachment.s3_protocol(style)}://#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
|
94
111
|
end unless Paperclip::Interpolations.respond_to? :s3_domain_url
|
112
|
+
Paperclip.interpolates(:asset_host) do |attachment, style|
|
113
|
+
"#{attachment.path(style).gsub(%r{^/}, "")}"
|
114
|
+
end unless Paperclip::Interpolations.respond_to? :asset_host
|
95
115
|
end
|
96
116
|
|
97
117
|
def expiring_url(time = 3600, style_name = default_style)
|
98
|
-
AWS::S3::S3Object.url_for(path(style_name), bucket_name, :expires_in => time, :use_ssl => (s3_protocol == 'https'))
|
118
|
+
AWS::S3::S3Object.url_for(path(style_name), bucket_name, :expires_in => time, :use_ssl => (s3_protocol(style_name) == 'https'))
|
99
119
|
end
|
100
120
|
|
101
121
|
def bucket_name
|
102
122
|
@bucket
|
103
123
|
end
|
104
124
|
|
125
|
+
def s3_host_name
|
126
|
+
@s3_host_name || "s3.amazonaws.com"
|
127
|
+
end
|
128
|
+
|
129
|
+
def set_permissions permissions
|
130
|
+
if permissions.is_a?(Hash)
|
131
|
+
permissions[:default] = permissions[:default] || :public_read
|
132
|
+
else
|
133
|
+
permissions = { :default => permissions || :public_read }
|
134
|
+
end
|
135
|
+
permissions
|
136
|
+
end
|
137
|
+
|
105
138
|
def s3_host_alias
|
106
139
|
@s3_host_alias
|
107
140
|
end
|
108
141
|
|
109
142
|
def parse_credentials creds
|
110
143
|
creds = find_credentials(creds).stringify_keys
|
111
|
-
(
|
144
|
+
env = Object.const_defined?(:Rails) ? Rails.env : nil
|
145
|
+
(creds[env] || creds).symbolize_keys
|
112
146
|
end
|
113
147
|
|
114
148
|
def exists?(style = default_style)
|
@@ -119,8 +153,12 @@ module Paperclip
|
|
119
153
|
end
|
120
154
|
end
|
121
155
|
|
122
|
-
def s3_protocol
|
123
|
-
@s3_protocol
|
156
|
+
def s3_protocol(style)
|
157
|
+
if @s3_protocol.is_a?(Proc)
|
158
|
+
@s3_protocol.call(style)
|
159
|
+
else
|
160
|
+
@s3_protocol
|
161
|
+
end
|
124
162
|
end
|
125
163
|
|
126
164
|
# Returns representation of the data of the file assigned to the given
|
@@ -148,8 +186,8 @@ module Paperclip
|
|
148
186
|
AWS::S3::S3Object.store(path(style),
|
149
187
|
file,
|
150
188
|
bucket_name,
|
151
|
-
{:content_type =>
|
152
|
-
:access => @s3_permissions,
|
189
|
+
{:content_type => file.content_type.to_s.strip,
|
190
|
+
:access => (@s3_permissions[style] || @s3_permissions[:default]),
|
153
191
|
}.merge(@s3_headers))
|
154
192
|
rescue AWS::S3::NoSuchBucket => e
|
155
193
|
create_bucket
|