dm-paperclip 2.4.1 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +29 -0
- data/Gemfile.lock +100 -0
- data/README.md +145 -0
- data/Rakefile +37 -71
- data/VERSION +1 -0
- data/dm-paperclip.gemspec +103 -0
- data/lib/dm-paperclip.rb +88 -74
- data/lib/dm-paperclip/attachment.rb +139 -102
- data/lib/dm-paperclip/callbacks.rb +55 -0
- data/lib/dm-paperclip/command_line.rb +86 -0
- data/lib/dm-paperclip/ext/blank.rb +24 -0
- data/lib/dm-paperclip/ext/class.rb +50 -0
- data/lib/dm-paperclip/ext/compatibility.rb +11 -0
- data/lib/dm-paperclip/ext/try_dup.rb +12 -0
- data/lib/dm-paperclip/geometry.rb +3 -5
- data/lib/dm-paperclip/interpolations.rb +57 -32
- data/lib/dm-paperclip/iostream.rb +12 -26
- data/lib/dm-paperclip/processor.rb +14 -4
- data/lib/dm-paperclip/storage.rb +2 -257
- data/lib/dm-paperclip/storage/filesystem.rb +73 -0
- data/lib/dm-paperclip/storage/s3.rb +209 -0
- data/lib/dm-paperclip/storage/s3/aws_library.rb +41 -0
- data/lib/dm-paperclip/storage/s3/aws_s3_library.rb +60 -0
- data/lib/dm-paperclip/style.rb +90 -0
- data/lib/dm-paperclip/thumbnail.rb +33 -24
- data/lib/dm-paperclip/upfile.rb +13 -5
- data/lib/dm-paperclip/validations.rb +40 -37
- data/lib/dm-paperclip/version.rb +4 -0
- data/test/attachment_test.rb +510 -67
- data/test/command_line_test.rb +138 -0
- data/test/fixtures/s3.yml +8 -0
- data/test/fixtures/twopage.pdf +0 -0
- data/test/fixtures/uppercase.PNG +0 -0
- data/test/geometry_test.rb +54 -19
- data/test/helper.rb +91 -28
- data/test/integration_test.rb +252 -79
- data/test/interpolations_test.rb +150 -0
- data/test/iostream_test.rb +8 -15
- data/test/paperclip_test.rb +222 -69
- data/test/processor_test.rb +10 -0
- data/test/storage_test.rb +102 -23
- data/test/style_test.rb +141 -0
- data/test/thumbnail_test.rb +106 -18
- data/test/upfile_test.rb +36 -0
- metadata +136 -121
- data/README.rdoc +0 -116
- data/init.rb +0 -1
- data/lib/dm-paperclip/callback_compatability.rb +0 -33
data/lib/dm-paperclip.rb
CHANGED
@@ -26,26 +26,33 @@
|
|
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
|
|
31
|
-
require 'extlib'
|
32
32
|
require 'dm-core'
|
33
|
+
require 'extlib'
|
33
34
|
|
35
|
+
require 'dm-paperclip/ext/compatibility'
|
36
|
+
require 'dm-paperclip/ext/class'
|
37
|
+
require 'dm-paperclip/ext/blank'
|
38
|
+
require 'dm-paperclip/ext/try_dup'
|
39
|
+
require 'dm-paperclip/version'
|
34
40
|
require 'dm-paperclip/upfile'
|
35
41
|
require 'dm-paperclip/iostream'
|
36
42
|
require 'dm-paperclip/geometry'
|
37
43
|
require 'dm-paperclip/processor'
|
38
44
|
require 'dm-paperclip/thumbnail'
|
39
|
-
require 'dm-paperclip/storage'
|
40
45
|
require 'dm-paperclip/interpolations'
|
46
|
+
require 'dm-paperclip/style'
|
41
47
|
require 'dm-paperclip/attachment'
|
48
|
+
require 'dm-paperclip/storage'
|
49
|
+
require 'dm-paperclip/command_line'
|
50
|
+
require 'dm-paperclip/callbacks'
|
42
51
|
|
43
52
|
# The base module that gets included in ActiveRecord::Base. See the
|
44
53
|
# documentation for Paperclip::ClassMethods for more useful information.
|
45
54
|
module Paperclip
|
46
55
|
|
47
|
-
VERSION = "2.4.1"
|
48
|
-
|
49
56
|
# To configure Paperclip, put this code in an initializer, Rake task, or wherever:
|
50
57
|
#
|
51
58
|
# Paperclip.configure do |config|
|
@@ -104,12 +111,12 @@ module Paperclip
|
|
104
111
|
class << self
|
105
112
|
|
106
113
|
# Provides configurability to Paperclip. There are a number of options available, such as:
|
107
|
-
# * whiny: Will raise an error if Paperclip cannot process thumbnails of
|
114
|
+
# * whiny: Will raise an error if Paperclip cannot process thumbnails of
|
108
115
|
# an uploaded image. Defaults to true.
|
109
116
|
# * log: Logs progress to the Rails log. Uses ActiveRecord's logger, so honors
|
110
117
|
# log levels, etc. Defaults to true.
|
111
118
|
# * command_path: Defines the path at which to find the command line
|
112
|
-
# programs if they are not visible to Rails the system's search path. Defaults to
|
119
|
+
# programs if they are not visible to Rails the system's search path. Defaults to
|
113
120
|
# nil, which uses the first executable found in the user's search path.
|
114
121
|
# * image_magick_path: Deprecated alias of command_path.
|
115
122
|
def options
|
@@ -118,7 +125,7 @@ module Paperclip
|
|
118
125
|
:image_magick_path => nil,
|
119
126
|
:command_path => nil,
|
120
127
|
:log => true,
|
121
|
-
:log_command =>
|
128
|
+
:log_command => true,
|
122
129
|
:swallow_stderr => true
|
123
130
|
}
|
124
131
|
end
|
@@ -135,7 +142,7 @@ module Paperclip
|
|
135
142
|
Paperclip::Interpolations[key] = block
|
136
143
|
end
|
137
144
|
|
138
|
-
# The run method takes a command to execute and
|
145
|
+
# The run method takes a command to execute and an array of parameters
|
139
146
|
# that get passed to it. The command is prefixed with the :command_path
|
140
147
|
# option from Paperclip.options. If you have many commands to run and
|
141
148
|
# they are in different paths, the suggested course of action is to
|
@@ -144,23 +151,19 @@ module Paperclip
|
|
144
151
|
# If the command returns with a result code that is not one of the
|
145
152
|
# expected_outcodes, a PaperclipCommandLineError will be raised. Generally
|
146
153
|
# a code of 0 is expected, but a list of codes may be passed if necessary.
|
154
|
+
# These codes should be passed as a hash as the last argument, like so:
|
155
|
+
#
|
156
|
+
# Paperclip.run("echo", "something", :expected_outcodes => [0,1,2,3])
|
147
157
|
#
|
148
|
-
# This method can log the command being run when
|
158
|
+
# This method can log the command being run when
|
149
159
|
# Paperclip.options[:log_command] is set to true (defaults to false). This
|
150
160
|
# will only log if logging in general is set to true as well.
|
151
|
-
def run cmd, params
|
152
|
-
|
153
|
-
|
154
|
-
Paperclip.log(command) if Paperclip.options[:log_command]
|
155
|
-
output = `#{command}`
|
156
|
-
unless [expected_outcodes].flatten.include?($?.exitstatus)
|
157
|
-
raise PaperclipCommandLineError, "Error while running #{cmd}"
|
161
|
+
def run cmd, *params
|
162
|
+
if options[:image_magick_path]
|
163
|
+
Paperclip.log("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
|
158
164
|
end
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
def bit_bucket #:nodoc:
|
163
|
-
File.exists?("/dev/null") ? "/dev/null" : "NUL"
|
165
|
+
CommandLine.path = options[:command_path] || options[:image_magick_path]
|
166
|
+
CommandLine.new(cmd, *params).run
|
164
167
|
end
|
165
168
|
|
166
169
|
def included base #:nodoc:
|
@@ -171,7 +174,7 @@ module Paperclip
|
|
171
174
|
end
|
172
175
|
|
173
176
|
def processor name #:nodoc:
|
174
|
-
name = name.to_s
|
177
|
+
name = DataMapper::Inflector.classify(name.to_s)
|
175
178
|
processor = Paperclip.const_get(name)
|
176
179
|
unless processor.ancestors.include?(Paperclip::Processor)
|
177
180
|
raise PaperclipError.new("[paperclip] Processor #{name} was not found")
|
@@ -179,6 +182,12 @@ module Paperclip
|
|
179
182
|
processor
|
180
183
|
end
|
181
184
|
|
185
|
+
def each_instance_with_attachment(klass, name)
|
186
|
+
Object.const_get(klass).all.each do |instance|
|
187
|
+
yield(instance) if instance.send(:"#{name}?")
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
182
191
|
# Log a paperclip-specific line. Uses ActiveRecord::Base.logger
|
183
192
|
# by default. Set Paperclip.options[:log] to false to turn off.
|
184
193
|
def log message
|
@@ -197,31 +206,30 @@ module Paperclip
|
|
197
206
|
class PaperclipError < StandardError #:nodoc:
|
198
207
|
end
|
199
208
|
|
200
|
-
class PaperclipCommandLineError <
|
209
|
+
class PaperclipCommandLineError < PaperclipError #:nodoc:
|
210
|
+
attr_accessor :output
|
211
|
+
def initialize(msg = nil, output = nil)
|
212
|
+
super(msg)
|
213
|
+
@output = output
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
class StorageMethodNotFound < PaperclipError
|
218
|
+
end
|
219
|
+
|
220
|
+
class CommandNotFoundError < PaperclipError
|
201
221
|
end
|
202
222
|
|
203
223
|
class NotIdentifiedByImageMagickError < PaperclipError #:nodoc:
|
204
224
|
end
|
205
|
-
|
225
|
+
|
206
226
|
class InfiniteInterpolationError < PaperclipError #:nodoc:
|
207
227
|
end
|
208
|
-
|
209
|
-
module Resource
|
210
228
|
|
229
|
+
module Resource
|
211
230
|
def self.included(base)
|
212
|
-
|
213
|
-
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
214
|
-
class_variable_set(:@@attachment_definitions,nil) unless class_variable_defined?(:@@attachment_definitions)
|
215
|
-
def self.attachment_definitions
|
216
|
-
@@attachment_definitions
|
217
|
-
end
|
218
|
-
|
219
|
-
def self.attachment_definitions=(obj)
|
220
|
-
@@attachment_definitions = obj
|
221
|
-
end
|
222
|
-
RUBY
|
223
|
-
|
224
231
|
base.extend Paperclip::ClassMethods
|
232
|
+
base.extend Paperclip::Ext::Class::Hook
|
225
233
|
|
226
234
|
# Done at this time to ensure that the user
|
227
235
|
# had a chance to configure the app in an initializer
|
@@ -232,50 +240,50 @@ module Paperclip
|
|
232
240
|
end
|
233
241
|
|
234
242
|
Paperclip.require_processors
|
235
|
-
|
236
243
|
end
|
237
|
-
|
238
244
|
end
|
239
245
|
|
240
246
|
module ClassMethods
|
241
247
|
# +has_attached_file+ gives the class it is called on an attribute that maps to a file. This
|
242
|
-
# is typically a file stored somewhere on the filesystem and has been uploaded by a user.
|
248
|
+
# is typically a file stored somewhere on the filesystem and has been uploaded by a user.
|
243
249
|
# The attribute returns a Paperclip::Attachment object which handles the management of
|
244
|
-
# that file. The intent is to make the attachment as much like a normal attribute. The
|
245
|
-
# thumbnails will be created when the new file is assigned, but they will *not* be saved
|
246
|
-
# until +save+ is called on the record. Likewise, if the attribute is set to +nil+ is
|
247
|
-
# called on it, the attachment will *not* be deleted until +save+ is called. See the
|
248
|
-
# Paperclip::Attachment documentation for more specifics. There are a number of options
|
250
|
+
# that file. The intent is to make the attachment as much like a normal attribute. The
|
251
|
+
# thumbnails will be created when the new file is assigned, but they will *not* be saved
|
252
|
+
# until +save+ is called on the record. Likewise, if the attribute is set to +nil+ is
|
253
|
+
# called on it, the attachment will *not* be deleted until +save+ is called. See the
|
254
|
+
# Paperclip::Attachment documentation for more specifics. There are a number of options
|
249
255
|
# you can set to change the behavior of a Paperclip attachment:
|
250
256
|
# * +url+: The full URL of where the attachment is publically accessible. This can just
|
251
257
|
# as easily point to a directory served directly through Apache as it can to an action
|
252
258
|
# that can control permissions. You can specify the full domain and path, but usually
|
253
|
-
# just an absolute path is sufficient. The leading slash must be included manually for
|
254
|
-
# absolute paths. The default value is
|
259
|
+
# just an absolute path is sufficient. The leading slash *must* be included manually for
|
260
|
+
# absolute paths. The default value is
|
261
|
+
# "/system/:attachment/:id/:style/:filename". See
|
255
262
|
# Paperclip::Attachment#interpolate for more information on variable interpolaton.
|
256
|
-
# :url => "/:attachment/:id/:style_:
|
263
|
+
# :url => "/:class/:attachment/:id/:style_:filename"
|
257
264
|
# :url => "http://some.other.host/stuff/:class/:id_:extension"
|
258
|
-
# * +default_url+: The URL that will be returned if there is no attachment assigned.
|
259
|
-
# This field is interpolated just as the url is. The default value is
|
260
|
-
# "/:
|
265
|
+
# * +default_url+: The URL that will be returned if there is no attachment assigned.
|
266
|
+
# This field is interpolated just as the url is. The default value is
|
267
|
+
# "/:attachment/:style/missing.png"
|
261
268
|
# has_attached_file :avatar, :default_url => "/images/default_:style_avatar.png"
|
262
269
|
# User.new.avatar_url(:small) # => "/images/default_small_avatar.png"
|
263
|
-
# * +styles+: A hash of thumbnail styles and their geometries. You can find more about
|
264
|
-
# geometry strings at the ImageMagick website
|
270
|
+
# * +styles+: A hash of thumbnail styles and their geometries. You can find more about
|
271
|
+
# geometry strings at the ImageMagick website
|
265
272
|
# (http://www.imagemagick.org/script/command-line-options.php#resize). Paperclip
|
266
|
-
# also adds the "#" option (e.g. "50x50#"), which will resize the image to fit maximally
|
267
|
-
# inside the dimensions and then crop the rest off (weighted at the center). The
|
273
|
+
# also adds the "#" option (e.g. "50x50#"), which will resize the image to fit maximally
|
274
|
+
# inside the dimensions and then crop the rest off (weighted at the center). The
|
268
275
|
# default value is to generate no thumbnails.
|
269
|
-
# * +default_style+: The thumbnail style that will be used by default URLs.
|
276
|
+
# * +default_style+: The thumbnail style that will be used by default URLs.
|
270
277
|
# Defaults to +original+.
|
271
278
|
# has_attached_file :avatar, :styles => { :normal => "100x100#" },
|
272
279
|
# :default_style => :normal
|
273
280
|
# user.avatar.url # => "/avatars/23/normal_me.png"
|
274
|
-
# * +
|
275
|
-
#
|
276
|
-
# Defaults to true.
|
281
|
+
# * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due
|
282
|
+
# to a command line error. This will override the global setting for this attachment.
|
283
|
+
# Defaults to true. This option used to be called :whiny_thumbanils, but this is
|
284
|
+
# deprecated.
|
277
285
|
# * +convert_options+: When creating thumbnails, use this free-form options
|
278
|
-
#
|
286
|
+
# array to pass in various convert command options. Typical options are "-strip" to
|
279
287
|
# remove all Exif data from the image (save space for thumbnails and avatars) or
|
280
288
|
# "-depth 8" to specify the bit depth of the resulting conversion. See ImageMagick
|
281
289
|
# convert documentation for more options: (http://www.imagemagick.org/script/convert.php)
|
@@ -283,12 +291,18 @@ module Paperclip
|
|
283
291
|
# of thumbnail being generated. You can also specify :all as a key, which will apply
|
284
292
|
# to all of the thumbnails being generated. If you specify options for the :original,
|
285
293
|
# it would be best if you did not specify destructive options, as the intent of keeping
|
286
|
-
# the original around is to regenerate all the thumbnails
|
294
|
+
# the original around is to regenerate all the thumbnails when requirements change.
|
287
295
|
# has_attached_file :avatar, :styles => { :large => "300x300", :negative => "100x100" }
|
288
296
|
# :convert_options => {
|
289
297
|
# :all => "-strip",
|
290
298
|
# :negative => "-negate"
|
291
299
|
# }
|
300
|
+
# NOTE: While not deprecated yet, it is not recommended to specify options this way.
|
301
|
+
# It is recommended that :convert_options option be included in the hash passed to each
|
302
|
+
# :styles for compatability with future versions.
|
303
|
+
# NOTE: Strings supplied to :convert_options are split on space in order to undergo
|
304
|
+
# shell quoting for safety. If your options require a space, please pre-split them
|
305
|
+
# and pass an array to :convert_options instead.
|
292
306
|
# * +storage+: Chooses the storage backend where the files will be stored. The current
|
293
307
|
# choices are :filesystem and :s3. The default is :filesystem. Make sure you read the
|
294
308
|
# documentation for Paperclip::Storage::Filesystem and Paperclip::Storage::S3
|
@@ -296,10 +310,11 @@ module Paperclip
|
|
296
310
|
def has_attached_file name, options = {}
|
297
311
|
include InstanceMethods
|
298
312
|
|
299
|
-
self
|
300
|
-
|
301
|
-
|
313
|
+
Paperclip::Ext::Class.write_inheritable_attribute(self, :attachment_definitions, {}) if attachment_definitions.nil?
|
314
|
+
attachment_definitions[name] = {:validations => []}.merge(options)
|
315
|
+
|
302
316
|
property_options = options.delete_if { |k,v| ![ :public, :protected, :private, :accessor, :reader, :writer ].include?(key) }
|
317
|
+
property_options[:required] = false
|
303
318
|
|
304
319
|
property :"#{name}_file_name", String, property_options.merge(:length => 255)
|
305
320
|
property :"#{name}_content_type", String, property_options.merge(:length => 255)
|
@@ -308,10 +323,9 @@ module Paperclip
|
|
308
323
|
|
309
324
|
after :save, :save_attached_files
|
310
325
|
before :destroy, :destroy_attached_files
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
# define_callbacks :"before_#{name}_post_process", :"after_#{name}_post_process"
|
326
|
+
|
327
|
+
Paperclip::Callbacks.define(self, "post_process")
|
328
|
+
Paperclip::Callbacks.define(self, "#{name}_post_process")
|
315
329
|
|
316
330
|
define_method name do |*args|
|
317
331
|
a = attachment_for(name)
|
@@ -323,11 +337,11 @@ module Paperclip
|
|
323
337
|
end
|
324
338
|
|
325
339
|
define_method "#{name}?" do
|
326
|
-
|
340
|
+
attachment_for(name).file?
|
327
341
|
end
|
328
342
|
|
329
343
|
if Paperclip.config.use_dm_validations
|
330
|
-
|
344
|
+
validators.add(Paperclip::Validate::CopyAttachmentErrors, name)
|
331
345
|
end
|
332
346
|
|
333
347
|
end
|
@@ -335,14 +349,14 @@ module Paperclip
|
|
335
349
|
# Returns the attachment definitions defined by each call to
|
336
350
|
# has_attached_file.
|
337
351
|
def attachment_definitions
|
338
|
-
read_inheritable_attribute(:attachment_definitions)
|
352
|
+
Paperclip::Ext::Class.read_inheritable_attribute(self, :attachment_definitions)
|
339
353
|
end
|
340
354
|
end
|
341
355
|
|
342
356
|
module InstanceMethods #:nodoc:
|
343
357
|
def attachment_for name
|
344
|
-
@
|
345
|
-
@
|
358
|
+
@_paperclip_attachments ||= {}
|
359
|
+
@_paperclip_attachments[name] ||= Attachment.new(name, self, self.class.attachment_definitions[name])
|
346
360
|
end
|
347
361
|
|
348
362
|
def each_attachment
|
@@ -1,23 +1,31 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
module Paperclip
|
2
3
|
# The Attachment class manages the files for a given attachment. It saves
|
3
4
|
# when the model saves, deletes when the model is destroyed, and processes
|
4
5
|
# the file upon assignment.
|
5
6
|
class Attachment
|
6
|
-
|
7
|
+
include IOStream
|
7
8
|
def self.default_options
|
8
9
|
@default_options ||= {
|
9
|
-
:url
|
10
|
-
:path
|
11
|
-
:styles
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
10
|
+
:url => "/system/:attachment/:id/:style/:filename",
|
11
|
+
:path => ":web_root/public:url",
|
12
|
+
:styles => {},
|
13
|
+
:processors => [:thumbnail],
|
14
|
+
:convert_options => {},
|
15
|
+
:default_url => "/:attachment/:style/missing.png",
|
16
|
+
:default_style => :original,
|
17
|
+
:validations => [],
|
18
|
+
:storage => :filesystem,
|
19
|
+
:use_timestamp => true,
|
20
|
+
:whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
|
21
|
+
:use_default_time_zone => true,
|
22
|
+
:hash_digest => "SHA1",
|
23
|
+
:hash_data => ":class/:attachment/:id/:style/:updated_at"
|
17
24
|
}
|
18
25
|
end
|
19
26
|
|
20
|
-
attr_reader :name, :instance, :
|
27
|
+
attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny, :options
|
28
|
+
attr_accessor :post_processing
|
21
29
|
|
22
30
|
# Creates an Attachment object. +name+ is the name of the attachment,
|
23
31
|
# +instance+ is the ActiveRecord object instance it's attached to, and
|
@@ -28,38 +36,55 @@ module Paperclip
|
|
28
36
|
|
29
37
|
options = self.class.default_options.merge(options)
|
30
38
|
|
31
|
-
@url
|
32
|
-
@url
|
33
|
-
@path
|
34
|
-
@path
|
35
|
-
@styles
|
36
|
-
@
|
37
|
-
@default_url
|
38
|
-
@validations
|
39
|
-
@default_style
|
40
|
-
@storage
|
41
|
-
@
|
42
|
-
@
|
43
|
-
@
|
44
|
-
@
|
45
|
-
@
|
46
|
-
@
|
47
|
-
@
|
48
|
-
@
|
49
|
-
@
|
39
|
+
@url = options[:url]
|
40
|
+
@url = @url.call(self) if @url.is_a?(Proc)
|
41
|
+
@path = options[:path]
|
42
|
+
@path = @path.call(self) if @path.is_a?(Proc)
|
43
|
+
@styles = options[:styles]
|
44
|
+
@normalized_styles = nil
|
45
|
+
@default_url = options[:default_url]
|
46
|
+
@validations = options[:validations]
|
47
|
+
@default_style = options[:default_style]
|
48
|
+
@storage = options[:storage]
|
49
|
+
@use_timestamp = options[:use_timestamp]
|
50
|
+
@whiny = options[:whiny_thumbnails] || options[:whiny]
|
51
|
+
@use_default_time_zone = options[:use_default_time_zone]
|
52
|
+
@hash_digest = options[:hash_digest]
|
53
|
+
@hash_data = options[:hash_data]
|
54
|
+
@hash_secret = options[:hash_secret]
|
55
|
+
@convert_options = options[:convert_options]
|
56
|
+
@processors = options[:processors]
|
57
|
+
@options = options
|
58
|
+
@post_processing = true
|
59
|
+
@queued_for_delete = []
|
60
|
+
@queued_for_write = {}
|
61
|
+
@errors = {}
|
62
|
+
@validation_errors = nil
|
63
|
+
@dirty = false
|
50
64
|
|
51
|
-
normalize_style_definition
|
52
65
|
initialize_storage
|
53
66
|
end
|
54
67
|
|
68
|
+
def styles
|
69
|
+
unless @normalized_styles
|
70
|
+
@normalized_styles = {}
|
71
|
+
(@styles.respond_to?(:call) ? @styles.call(self) : @styles).each do |name, args|
|
72
|
+
@normalized_styles[name] = Paperclip::Style.new(name, args.dup, self)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
@normalized_styles
|
76
|
+
end
|
77
|
+
|
78
|
+
def processors
|
79
|
+
@processors.respond_to?(:call) ? @processors.call(instance) : @processors
|
80
|
+
end
|
81
|
+
|
55
82
|
# What gets called when you call instance.attachment = File. It clears
|
56
|
-
# errors, assigns attributes, processes the file
|
83
|
+
# errors, assigns attributes, and processes the file. It
|
57
84
|
# also queues up the previous file for deletion, to be flushed away on
|
58
85
|
# #save of its host. In addition to form uploads, you can also assign
|
59
86
|
# another Paperclip attachment:
|
60
87
|
# new_user.avatar = old_user.avatar
|
61
|
-
# If the file that is assigned is not valid, the processing (i.e.
|
62
|
-
# thumbnailing, etc) will NOT be run.
|
63
88
|
def assign uploaded_file
|
64
89
|
|
65
90
|
ensure_required_accessors!
|
@@ -77,28 +102,29 @@ module Paperclip
|
|
77
102
|
return nil if uploaded_file.nil?
|
78
103
|
|
79
104
|
if uploaded_file.respond_to?(:[])
|
80
|
-
uploaded_file = uploaded_file
|
81
|
-
|
105
|
+
uploaded_file = DataMapper::Mash.new(uploaded_file)
|
106
|
+
|
82
107
|
@queued_for_write[:original] = uploaded_file['tempfile']
|
83
108
|
instance_write(:file_name, uploaded_file['filename'].strip.gsub(/[^\w\d\.\-]+/, '_'))
|
84
|
-
instance_write(:content_type, uploaded_file['content_type']
|
109
|
+
instance_write(:content_type, ( uploaded_file['content_type'] && uploaded_file['content_type'].strip || # sometimes it is 'type' instead of 'content_type'
|
110
|
+
uploaded_file['type'] && uploaded_file['type'].strip ||
|
111
|
+
uploaded_file['tempfile'].content_type.to_s.strip))
|
85
112
|
instance_write(:file_size, uploaded_file['size'] ? uploaded_file['size'].to_i : uploaded_file['tempfile'].size.to_i)
|
86
|
-
instance_write(:updated_at, Time.now)
|
87
113
|
else
|
88
|
-
@queued_for_write[:original] = uploaded_file
|
89
|
-
instance_write(:file_name, uploaded_file.original_filename.strip
|
114
|
+
@queued_for_write[:original] = to_tempfile(uploaded_file)
|
115
|
+
instance_write(:file_name, uploaded_file.original_filename.strip)
|
90
116
|
instance_write(:content_type, uploaded_file.content_type.to_s.strip)
|
91
117
|
instance_write(:file_size, uploaded_file.size.to_i)
|
92
|
-
instance_write(:updated_at, Time.now)
|
93
118
|
end
|
94
119
|
|
95
120
|
@dirty = true
|
96
121
|
|
97
|
-
post_process if valid?
|
122
|
+
post_process if @post_processing && valid?
|
98
123
|
|
99
124
|
# Reset the file size if the original file was reprocessed.
|
100
|
-
instance_write(:file_size,
|
101
|
-
|
125
|
+
instance_write(:file_size, @queued_for_write[:original].size.to_i)
|
126
|
+
instance_write(:fingerprint, generate_fingerprint(@queued_for_write[:original]))
|
127
|
+
instance_write(:updated_at, DateTime.now)
|
102
128
|
ensure
|
103
129
|
uploaded_file.close if close_uploaded_file
|
104
130
|
validate
|
@@ -108,25 +134,24 @@ module Paperclip
|
|
108
134
|
# this does not necessarily need to point to a file that your web server
|
109
135
|
# can access and can point to an action in your app, if you need fine
|
110
136
|
# grained security. This is not recommended if you don't need the
|
111
|
-
# security, however, for performance reasons.
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
include_updated_timestamp && updated_at ? [the_url, updated_at.to_time.to_i].compact.join(the_url.include?("?") ? "&" : "?") : the_url
|
137
|
+
# security, however, for performance reasons. Set use_timestamp to false
|
138
|
+
# if you want to stop the attachment update time appended to the url
|
139
|
+
def url(style_name = default_style, use_timestamp = @use_timestamp)
|
140
|
+
url = original_filename.nil? ? interpolate(@default_url, style_name) : interpolate(@url, style_name)
|
141
|
+
use_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
|
117
142
|
end
|
118
143
|
|
119
144
|
# Returns the path of the attachment as defined by the :path option. If the
|
120
145
|
# file is stored in the filesystem the path refers to the path of the file
|
121
146
|
# on disk. If the file is stored in S3, the path is the "key" part of the
|
122
147
|
# URL, and the :bucket option refers to the S3 bucket.
|
123
|
-
def path
|
124
|
-
original_filename.nil? ? nil : interpolate(@path,
|
148
|
+
def path style_name = default_style
|
149
|
+
original_filename.nil? ? nil : interpolate(@path, style_name)
|
125
150
|
end
|
126
151
|
|
127
152
|
# Alias to +url+
|
128
|
-
def to_s
|
129
|
-
url(
|
153
|
+
def to_s style_name = default_style
|
154
|
+
url(style_name)
|
130
155
|
end
|
131
156
|
|
132
157
|
# Returns true if there are no errors on this attachment.
|
@@ -188,6 +213,12 @@ module Paperclip
|
|
188
213
|
instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
|
189
214
|
end
|
190
215
|
|
216
|
+
# Returns the hash of the file as originally assigned, and lives in the
|
217
|
+
# <attachment>_fingerprint attribute of the model.
|
218
|
+
def fingerprint
|
219
|
+
instance_read(:fingerprint) || (@queued_for_write[:original] && generate_fingerprint(@queued_for_write[:original]))
|
220
|
+
end
|
221
|
+
|
191
222
|
# Returns the content_type of the file as originally assigned, and lives
|
192
223
|
# in the <attachment>_content_type attribute of the model.
|
193
224
|
def content_type
|
@@ -197,7 +228,29 @@ module Paperclip
|
|
197
228
|
# Returns the last modified time of the file as originally assigned, and
|
198
229
|
# lives in the <attachment>_updated_at attribute of the model.
|
199
230
|
def updated_at
|
200
|
-
instance_read(:updated_at)
|
231
|
+
time = instance_read(:updated_at)
|
232
|
+
return nil unless time
|
233
|
+
|
234
|
+
if time.is_a?(DateTime)
|
235
|
+
time && ((time - ::DateTime.civil(1970)) * 86_400).to_i
|
236
|
+
else
|
237
|
+
time.to_i
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Returns a unique hash suitable for obfuscating the URL of an otherwise
|
242
|
+
# publicly viewable attachment.
|
243
|
+
def hash(style_name = default_style)
|
244
|
+
raise ArgumentError, "Unable to generate hash without :hash_secret" unless @hash_secret
|
245
|
+
require 'openssl' unless defined?(OpenSSL)
|
246
|
+
data = interpolate(@hash_data, style_name)
|
247
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@hash_digest).new, @hash_secret, data)
|
248
|
+
end
|
249
|
+
|
250
|
+
def generate_fingerprint(source)
|
251
|
+
data = source.read
|
252
|
+
source.rewind if source.respond_to?(:rewind)
|
253
|
+
Digest::MD5.hexdigest(data)
|
201
254
|
end
|
202
255
|
|
203
256
|
# Paths and URLs can have a number of variables interpolated into them
|
@@ -216,15 +269,15 @@ module Paperclip
|
|
216
269
|
# in the paperclip:refresh rake task and that's it. It will regenerate all
|
217
270
|
# thumbnails forcefully, by reobtaining the original file and going through
|
218
271
|
# the post-process again.
|
219
|
-
def reprocess!
|
272
|
+
def reprocess!(*style_args)
|
220
273
|
new_original = Tempfile.new("paperclip-reprocess")
|
221
274
|
new_original.binmode
|
222
275
|
if old_original = to_file(:original)
|
223
|
-
new_original.write( old_original.read )
|
276
|
+
new_original.write( old_original.respond_to?(:get) ? old_original.get : old_original.read )
|
224
277
|
new_original.rewind
|
225
278
|
|
226
279
|
@queued_for_write = { :original => new_original }
|
227
|
-
post_process
|
280
|
+
post_process(*style_args)
|
228
281
|
|
229
282
|
old_original.close if old_original.respond_to?(:close)
|
230
283
|
|
@@ -232,11 +285,14 @@ module Paperclip
|
|
232
285
|
else
|
233
286
|
true
|
234
287
|
end
|
288
|
+
rescue Errno::EACCES => e
|
289
|
+
log "Skipping file: #{e.message}"
|
290
|
+
false
|
235
291
|
end
|
236
292
|
|
237
293
|
# Returns true if a file has been assigned.
|
238
294
|
def file?
|
239
|
-
!
|
295
|
+
!Paperclip::Ext.blank?(original_filename)
|
240
296
|
end
|
241
297
|
|
242
298
|
# Writes the attachment-specific attribute on the instance. For example,
|
@@ -301,7 +357,7 @@ module Paperclip
|
|
301
357
|
def check_guard guard #:nodoc:
|
302
358
|
if guard.respond_to? :call
|
303
359
|
guard.call(instance)
|
304
|
-
elsif !
|
360
|
+
elsif ! Paperclip::Ext.blank?(guard)
|
305
361
|
instance.send(guard.to_s)
|
306
362
|
end
|
307
363
|
end
|
@@ -318,8 +374,8 @@ module Paperclip
|
|
318
374
|
|
319
375
|
def validate_content_type options #:nodoc:
|
320
376
|
valid_types = [options[:content_type]].flatten
|
321
|
-
unless
|
322
|
-
unless
|
377
|
+
unless Paperclip::Ext.blank?(original_filename)
|
378
|
+
unless Paperclip::Ext.blank?(valid_types)
|
323
379
|
content_type = instance_read(:content_type)
|
324
380
|
unless valid_types.any?{|t| content_type.nil? || t === content_type }
|
325
381
|
options[:message] || "is not one of the allowed file types."
|
@@ -328,37 +384,13 @@ module Paperclip
|
|
328
384
|
end
|
329
385
|
end
|
330
386
|
|
331
|
-
def normalize_style_definition #:nodoc:
|
332
|
-
@styles.each do |name, args|
|
333
|
-
unless args.is_a? Hash
|
334
|
-
dimensions, format = [args, nil].flatten[0..1]
|
335
|
-
format = nil if format.blank?
|
336
|
-
@styles[name] = {
|
337
|
-
:processors => @processors,
|
338
|
-
:geometry => dimensions,
|
339
|
-
:format => format,
|
340
|
-
:whiny => @whiny,
|
341
|
-
:convert_options => extra_options_for(name)
|
342
|
-
}
|
343
|
-
else
|
344
|
-
@styles[name] = {
|
345
|
-
:processors => @processors,
|
346
|
-
:whiny => @whiny,
|
347
|
-
:convert_options => extra_options_for(name)
|
348
|
-
}.merge(@styles[name])
|
349
|
-
end
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
def solidify_style_definitions #:nodoc:
|
354
|
-
@styles.each do |name, args|
|
355
|
-
@styles[name][:geometry] = @styles[name][:geometry].call(instance) if @styles[name][:geometry].respond_to?(:call)
|
356
|
-
@styles[name][:processors] = @styles[name][:processors].call(instance) if @styles[name][:processors].respond_to?(:call)
|
357
|
-
end
|
358
|
-
end
|
359
|
-
|
360
387
|
def initialize_storage #:nodoc:
|
361
|
-
|
388
|
+
storage_class_name = @storage.to_s.capitalize
|
389
|
+
begin
|
390
|
+
@storage_module = Paperclip::Storage.const_get(storage_class_name)
|
391
|
+
rescue NameError
|
392
|
+
raise StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'"
|
393
|
+
end
|
362
394
|
self.extend(@storage_module)
|
363
395
|
end
|
364
396
|
|
@@ -371,18 +403,23 @@ module Paperclip
|
|
371
403
|
[ style_options, all_options ].compact.join(" ")
|
372
404
|
end
|
373
405
|
|
374
|
-
def post_process #:nodoc:
|
406
|
+
def post_process(*style_args) #:nodoc:
|
375
407
|
return if @queued_for_write[:original].nil?
|
376
|
-
|
377
|
-
|
408
|
+
Paperclip::Callbacks.run(instance, 'post_process') do
|
409
|
+
Paperclip::Callbacks.run(instance, "#{name}_post_process") do
|
410
|
+
post_process_styles(*style_args)
|
411
|
+
end
|
412
|
+
end
|
378
413
|
end
|
379
414
|
|
380
|
-
def post_process_styles #:nodoc:
|
381
|
-
|
415
|
+
def post_process_styles(*style_args) #:nodoc:
|
416
|
+
styles.each do |name, style|
|
382
417
|
begin
|
383
|
-
|
384
|
-
|
385
|
-
|
418
|
+
if style_args.empty? || style_args.include?(name)
|
419
|
+
raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.empty?
|
420
|
+
@queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
|
421
|
+
Paperclip.processor(processor).make(file, style.processor_options, self)
|
422
|
+
end
|
386
423
|
end
|
387
424
|
rescue PaperclipError => e
|
388
425
|
log("An error was received while processing: #{e.inspect}")
|
@@ -391,13 +428,13 @@ module Paperclip
|
|
391
428
|
end
|
392
429
|
end
|
393
430
|
|
394
|
-
def interpolate pattern,
|
395
|
-
Paperclip::Interpolations.interpolate(pattern, self,
|
431
|
+
def interpolate pattern, style_name = default_style #:nodoc:
|
432
|
+
Paperclip::Interpolations.interpolate(pattern, self, style_name)
|
396
433
|
end
|
397
434
|
|
398
435
|
def queue_existing_for_delete #:nodoc:
|
399
436
|
return unless file?
|
400
|
-
@queued_for_delete += [:original,
|
437
|
+
@queued_for_delete += [:original, *styles.keys].uniq.map do |style|
|
401
438
|
path(style) if exists?(style)
|
402
439
|
end.compact
|
403
440
|
instance_write(:file_name, nil)
|