carrierwave 1.3.4 → 2.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +117 -43
  3. data/lib/carrierwave/downloader/base.rb +93 -0
  4. data/lib/carrierwave/downloader/remote_file.rb +65 -0
  5. data/lib/carrierwave/locale/en.yml +5 -4
  6. data/lib/carrierwave/mount.rb +25 -19
  7. data/lib/carrierwave/mounter.rb +70 -47
  8. data/lib/carrierwave/orm/activerecord.rb +14 -8
  9. data/lib/carrierwave/processing/mini_magick.rb +100 -117
  10. data/lib/carrierwave/processing/rmagick.rb +1 -1
  11. data/lib/carrierwave/processing/vips.rb +284 -0
  12. data/lib/carrierwave/processing.rb +1 -0
  13. data/lib/carrierwave/sanitized_file.rb +38 -16
  14. data/lib/carrierwave/storage/file.rb +2 -2
  15. data/lib/carrierwave/storage/fog.rb +44 -13
  16. data/lib/carrierwave/storage.rb +1 -0
  17. data/lib/carrierwave/uploader/cache.rb +24 -16
  18. data/lib/carrierwave/uploader/configuration.rb +28 -15
  19. data/lib/carrierwave/uploader/content_type_blacklist.rb +17 -8
  20. data/lib/carrierwave/uploader/content_type_whitelist.rb +20 -8
  21. data/lib/carrierwave/uploader/download.rb +2 -123
  22. data/lib/carrierwave/uploader/extension_blacklist.rb +18 -10
  23. data/lib/carrierwave/uploader/extension_whitelist.rb +19 -10
  24. data/lib/carrierwave/uploader/mountable.rb +6 -0
  25. data/lib/carrierwave/uploader/processing.rb +11 -1
  26. data/lib/carrierwave/uploader/proxy.rb +2 -2
  27. data/lib/carrierwave/uploader/serialization.rb +1 -1
  28. data/lib/carrierwave/uploader/store.rb +5 -3
  29. data/lib/carrierwave/uploader/url.rb +6 -3
  30. data/lib/carrierwave/uploader/versions.rb +43 -13
  31. data/lib/carrierwave/uploader.rb +0 -9
  32. data/lib/carrierwave/validations/active_model.rb +3 -3
  33. data/lib/carrierwave/version.rb +1 -1
  34. data/lib/carrierwave.rb +4 -0
  35. data/lib/generators/templates/uploader.rb +2 -2
  36. metadata +100 -27
@@ -3,14 +3,17 @@ module CarrierWave
3
3
  # this is an internal class, used by CarrierWave::Mount so that
4
4
  # we don't pollute the model with a lot of methods.
5
5
  class Mounter #:nodoc:
6
- attr_reader :column, :record, :remote_urls, :integrity_error,
7
- :processing_error, :download_error
6
+ attr_reader :column, :record, :remote_urls, :integrity_errors,
7
+ :processing_errors, :download_errors
8
8
  attr_accessor :remove, :remote_request_headers
9
9
 
10
10
  def initialize(record, column, options={})
11
11
  @record = record
12
12
  @column = column
13
13
  @options = record.class.uploader_options[column]
14
+ @download_errors = []
15
+ @processing_errors = []
16
+ @integrity_errors = []
14
17
  end
15
18
 
16
19
  def uploader_class
@@ -38,21 +41,30 @@ module CarrierWave
38
41
  end
39
42
 
40
43
  def cache(new_files)
41
- return if not new_files or new_files == ""
44
+ return if !new_files.is_a?(Array) && new_files.blank?
45
+ old_uploaders = uploaders
42
46
  @uploaders = new_files.map do |new_file|
43
- uploader = blank_uploader
44
- uploader.cache!(new_file)
45
- uploader
46
- end
47
-
48
- @integrity_error = nil
49
- @processing_error = nil
50
- rescue CarrierWave::IntegrityError => e
51
- @integrity_error = e
52
- raise e unless option(:ignore_integrity_errors)
53
- rescue CarrierWave::ProcessingError => e
54
- @processing_error = e
55
- raise e unless option(:ignore_processing_errors)
47
+ handle_error do
48
+ if new_file.is_a?(String)
49
+ if (uploader = old_uploaders.detect { |uploader| uploader.identifier == new_file })
50
+ uploader.staged = true
51
+ uploader
52
+ else
53
+ begin
54
+ uploader = blank_uploader
55
+ uploader.retrieve_from_cache!(new_file)
56
+ uploader
57
+ rescue CarrierWave::InvalidParameter
58
+ nil
59
+ end
60
+ end
61
+ else
62
+ uploader = blank_uploader
63
+ uploader.cache!(new_file)
64
+ uploader
65
+ end
66
+ end
67
+ end.compact
56
68
  end
57
69
 
58
70
  def cache_names
@@ -60,45 +72,37 @@ module CarrierWave
60
72
  end
61
73
 
62
74
  def cache_names=(cache_names)
63
- return if not cache_names or cache_names == "" or uploaders.any?(&:cached?)
64
- @uploaders = cache_names.map do |cache_name|
65
- uploader = blank_uploader
66
- uploader.retrieve_from_cache!(cache_name)
67
- uploader
75
+ cache_names = cache_names.reject(&:blank?)
76
+ return if cache_names.blank?
77
+ clear_unstaged
78
+ cache_names.each do |cache_name|
79
+ begin
80
+ uploader = blank_uploader
81
+ uploader.retrieve_from_cache!(cache_name)
82
+ @uploaders << uploader
83
+ rescue CarrierWave::InvalidParameter
84
+ # ignore
85
+ end
68
86
  end
69
- rescue CarrierWave::InvalidParameter
70
87
  end
71
88
 
72
89
  def remote_urls=(urls)
73
- return if not urls or urls == "" or urls.all?(&:blank?)
90
+ return if urls.blank? || urls.all?(&:blank?)
74
91
 
75
92
  @remote_urls = urls
76
- @download_error = nil
77
- @integrity_error = nil
78
93
 
79
- @uploaders = urls.zip(remote_request_headers || []).map do |url, header|
80
- uploader = blank_uploader
81
- uploader.download!(url, header || {})
82
- uploader
94
+ clear_unstaged
95
+ urls.zip(remote_request_headers || []).each do |url, header|
96
+ handle_error do
97
+ uploader = blank_uploader
98
+ uploader.download!(url, header || {})
99
+ @uploaders << uploader
100
+ end
83
101
  end
84
-
85
- rescue CarrierWave::DownloadError => e
86
- @download_error = e
87
- raise e unless option(:ignore_download_errors)
88
- rescue CarrierWave::ProcessingError => e
89
- @processing_error = e
90
- raise e unless option(:ignore_processing_errors)
91
- rescue CarrierWave::IntegrityError => e
92
- @integrity_error = e
93
- raise e unless option(:ignore_integrity_errors)
94
102
  end
95
103
 
96
104
  def store!
97
- if remove?
98
- remove!
99
- else
100
- uploaders.reject(&:blank?).each(&:store!)
101
- end
105
+ uploaders.reject(&:blank?).each(&:store!)
102
106
  end
103
107
 
104
108
  def urls(*args)
@@ -118,6 +122,10 @@ module CarrierWave
118
122
  @uploaders = []
119
123
  end
120
124
 
125
+ def clear!
126
+ @uploaders = []
127
+ end
128
+
121
129
  def serialization_column
122
130
  option(:mount_on) || column
123
131
  end
@@ -146,9 +154,7 @@ module CarrierWave
146
154
  end.path
147
155
  end
148
156
  before.each do |uploader|
149
- if uploader.remove_previously_stored_files_after_update and not after_paths.include?(uploader.path)
150
- uploader.remove!
151
- end
157
+ uploader.remove! if uploader.remove_previously_stored_files_after_update && !after_paths.include?(uploader.path)
152
158
  end
153
159
  end
154
160
 
@@ -161,5 +167,22 @@ module CarrierWave
161
167
  self.uploader_options[name] ||= record.class.uploader_option(column, name)
162
168
  end
163
169
 
170
+ def clear_unstaged
171
+ @uploaders ||= []
172
+ @uploaders.keep_if(&:staged)
173
+ end
174
+
175
+ def handle_error
176
+ yield
177
+ rescue CarrierWave::DownloadError => e
178
+ @download_errors << e
179
+ raise e unless option(:ignore_download_errors)
180
+ rescue CarrierWave::ProcessingError => e
181
+ @processing_errors << e
182
+ raise e unless option(:ignore_processing_errors)
183
+ rescue CarrierWave::IntegrityError => e
184
+ @integrity_errors << e
185
+ raise e unless option(:ignore_integrity_errors)
186
+ end
164
187
  end # Mounter
165
188
  end # CarrierWave
@@ -12,7 +12,9 @@ module CarrierWave
12
12
  def mount_uploader(column, uploader=nil, options={}, &block)
13
13
  super
14
14
 
15
- class_eval <<-RUBY, __FILE__, __LINE__+1
15
+ mod = Module.new
16
+ prepend mod
17
+ mod.class_eval <<-RUBY, __FILE__, __LINE__+1
16
18
  def remote_#{column}_url=(url)
17
19
  column = _mounter(:#{column}).serialization_column
18
20
  __send__(:"\#{column}_will_change!")
@@ -27,7 +29,9 @@ module CarrierWave
27
29
  def mount_uploaders(column, uploader=nil, options={}, &block)
28
30
  super
29
31
 
30
- class_eval <<-RUBY, __FILE__, __LINE__+1
32
+ mod = Module.new
33
+ prepend mod
34
+ mod.class_eval <<-RUBY, __FILE__, __LINE__+1
31
35
  def remote_#{column}_urls=(url)
32
36
  column = _mounter(:#{column}).serialization_column
33
37
  __send__(:"\#{column}_will_change!")
@@ -52,15 +56,16 @@ module CarrierWave
52
56
  validates_processing_of column if uploader_option(column.to_sym, :validate_processing)
53
57
  validates_download_of column if uploader_option(column.to_sym, :validate_download)
54
58
 
55
- after_save :"store_#{column}!"
56
59
  before_save :"write_#{column}_identifier"
60
+ after_save :"store_previous_changes_for_#{column}"
57
61
  after_commit :"remove_#{column}!", :on => :destroy
58
62
  after_commit :"mark_remove_#{column}_false", :on => :update
59
-
60
- after_save :"store_previous_changes_for_#{column}"
61
63
  after_commit :"remove_previously_stored_#{column}", :on => :update
64
+ after_commit :"store_#{column}!", :on => [:create, :update]
62
65
 
63
- class_eval <<-RUBY, __FILE__, __LINE__+1
66
+ mod = Module.new
67
+ prepend mod
68
+ mod.class_eval <<-RUBY, __FILE__, __LINE__+1
64
69
  def #{column}=(new_file)
65
70
  column = _mounter(:#{column}).serialization_column
66
71
  if !(new_file.blank? && __send__(:#{column}).blank?)
@@ -72,8 +77,9 @@ module CarrierWave
72
77
 
73
78
  def remove_#{column}=(value)
74
79
  column = _mounter(:#{column}).serialization_column
75
- __send__(:"\#{column}_will_change!")
76
- super
80
+ result = super
81
+ __send__(:"\#{column}_will_change!") if _mounter(:#{column}).remove?
82
+ result
77
83
  end
78
84
 
79
85
  def remove_#{column}!
@@ -21,9 +21,11 @@ module CarrierWave
21
21
  # process :resize_to_fit => [200, 200]
22
22
  # end
23
23
  #
24
- # Or create your own helpers with the powerful manipulate! method. Check
25
- # out the ImageMagick docs at http://www.imagemagick.org/script/command-line-options.php for more
26
- # info
24
+ # Or create your own helpers with the powerful minimagick! method, which
25
+ # yields an ImageProcessing::Builder object. Check out the ImageProcessing
26
+ # docs at http://github.com/janko-m/image_processing and the list of all
27
+ # available ImageMagick options at
28
+ # http://www.imagemagick.org/script/command-line-options.php for more info.
27
29
  #
28
30
  # class MyUploader < CarrierWave::Uploader::Base
29
31
  # include CarrierWave::MiniMagick
@@ -31,23 +33,22 @@ module CarrierWave
31
33
  # process :radial_blur => 10
32
34
  #
33
35
  # def radial_blur(amount)
34
- # manipulate! do |img|
35
- # img.radial_blur(amount)
36
- # img = yield(img) if block_given?
37
- # img
36
+ # minimagick! do |builder|
37
+ # builder.radial_blur(amount)
38
+ # builder = yield(builder) if block_given?
39
+ # builder
38
40
  # end
39
41
  # end
40
42
  # end
41
43
  #
42
44
  # === Note
43
45
  #
44
- # MiniMagick is a mini replacement for RMagick that uses the command line
45
- # tool "mogrify" for image manipulation.
46
+ # The ImageProcessing gem uses MiniMagick, a mini replacement for RMagick
47
+ # that uses ImageMagick command-line tools, to build a "convert" command that
48
+ # performs the processing.
46
49
  #
47
50
  # You can find more information here:
48
51
  #
49
- # http://mini_magick.rubyforge.org/
50
- # and
51
52
  # https://github.com/minimagick/minimagick/
52
53
  #
53
54
  #
@@ -55,19 +56,7 @@ module CarrierWave
55
56
  extend ActiveSupport::Concern
56
57
 
57
58
  included do
58
- begin
59
- require "mini_magick"
60
- rescue LoadError => e
61
- e.message << " (You may need to install the mini_magick gem)"
62
- raise e
63
- end
64
-
65
- prepend Module.new {
66
- def initialize(*)
67
- super
68
- @format = nil
69
- end
70
- }
59
+ require "image_processing/mini_magick"
71
60
  end
72
61
 
73
62
  module ClassMethods
@@ -109,12 +98,11 @@ module CarrierWave
109
98
  #
110
99
  # image.convert(:png)
111
100
  #
112
- def convert(format, page=nil)
113
- @format = format
114
- @page = page
115
- manipulate! do |img|
116
- img = yield(img) if block_given?
117
- img
101
+ def convert(format, page=nil, &block)
102
+ minimagick!(block) do |builder|
103
+ builder = builder.convert(format)
104
+ builder = builder.loader(page: page) if page
105
+ builder
118
106
  end
119
107
  end
120
108
 
@@ -128,21 +116,18 @@ module CarrierWave
128
116
  #
129
117
  # [width (Integer)] the width to scale the image to
130
118
  # [height (Integer)] the height to scale the image to
119
+ # [combine_options (Hash)] additional ImageMagick options to apply before resizing
131
120
  #
132
121
  # === Yields
133
122
  #
134
123
  # [MiniMagick::Image] additional manipulations to perform
135
124
  #
136
- def resize_to_limit(width, height, combine_options: {})
137
- width = dimension_from width
138
- height = dimension_from height
139
- manipulate! do |img|
140
- img.combine_options do |cmd|
141
- cmd.resize "#{width}x#{height}>"
142
- append_combine_options cmd, combine_options
143
- end
144
- img = yield(img) if block_given?
145
- img
125
+ def resize_to_limit(width, height, combine_options: {}, &block)
126
+ width, height = resolve_dimensions(width, height)
127
+
128
+ minimagick!(block) do |builder|
129
+ builder.resize_to_limit(width, height)
130
+ .apply(combine_options)
146
131
  end
147
132
  end
148
133
 
@@ -155,21 +140,18 @@ module CarrierWave
155
140
  #
156
141
  # [width (Integer)] the width to scale the image to
157
142
  # [height (Integer)] the height to scale the image to
143
+ # [combine_options (Hash)] additional ImageMagick options to apply before resizing
158
144
  #
159
145
  # === Yields
160
146
  #
161
147
  # [MiniMagick::Image] additional manipulations to perform
162
148
  #
163
- def resize_to_fit(width, height, combine_options: {})
164
- width = dimension_from width
165
- height = dimension_from height
166
- manipulate! do |img|
167
- img.combine_options do |cmd|
168
- cmd.resize "#{width}x#{height}"
169
- append_combine_options cmd, combine_options
170
- end
171
- img = yield(img) if block_given?
172
- img
149
+ def resize_to_fit(width, height, combine_options: {}, &block)
150
+ width, height = resolve_dimensions(width, height)
151
+
152
+ minimagick!(block) do |builder|
153
+ builder.resize_to_fit(width, height)
154
+ .apply(combine_options)
173
155
  end
174
156
  end
175
157
 
@@ -183,37 +165,18 @@ module CarrierWave
183
165
  # [width (Integer)] the width to scale the image to
184
166
  # [height (Integer)] the height to scale the image to
185
167
  # [gravity (String)] the current gravity suggestion (default: 'Center'; options: 'NorthWest', 'North', 'NorthEast', 'West', 'Center', 'East', 'SouthWest', 'South', 'SouthEast')
168
+ # [combine_options (Hash)] additional ImageMagick options to apply before resizing
186
169
  #
187
170
  # === Yields
188
171
  #
189
172
  # [MiniMagick::Image] additional manipulations to perform
190
173
  #
191
- def resize_to_fill(width, height, gravity = 'Center', combine_options: {})
192
- width = dimension_from width
193
- height = dimension_from height
194
- manipulate! do |img|
195
- cols, rows = img[:dimensions]
196
- img.combine_options do |cmd|
197
- if width != cols || height != rows
198
- scale_x = width/cols.to_f
199
- scale_y = height/rows.to_f
200
- if scale_x >= scale_y
201
- cols = (scale_x * (cols + 0.5)).round
202
- rows = (scale_x * (rows + 0.5)).round
203
- cmd.resize "#{cols}"
204
- else
205
- cols = (scale_y * (cols + 0.5)).round
206
- rows = (scale_y * (rows + 0.5)).round
207
- cmd.resize "x#{rows}"
208
- end
209
- end
210
- cmd.gravity gravity
211
- cmd.background "rgba(255,255,255,0.0)"
212
- cmd.extent "#{width}x#{height}" if cols != width || rows != height
213
- append_combine_options cmd, combine_options
214
- end
215
- img = yield(img) if block_given?
216
- img
174
+ def resize_to_fill(width, height, gravity = 'Center', combine_options: {}, &block)
175
+ width, height = resolve_dimensions(width, height)
176
+
177
+ minimagick!(block) do |builder|
178
+ builder.resize_to_fill(width, height, gravity: gravity)
179
+ .apply(combine_options)
217
180
  end
218
181
  end
219
182
 
@@ -232,28 +195,18 @@ module CarrierWave
232
195
  # [height (Integer)] the height to scale the image to
233
196
  # [background (String, :transparent)] the color of the background as a hexcode, like "#ff45de"
234
197
  # [gravity (String)] how to position the image
198
+ # [combine_options (Hash)] additional ImageMagick options to apply before resizing
235
199
  #
236
200
  # === Yields
237
201
  #
238
202
  # [MiniMagick::Image] additional manipulations to perform
239
203
  #
240
- def resize_and_pad(width, height, background=:transparent, gravity='Center', combine_options: {})
241
- width = dimension_from width
242
- height = dimension_from height
243
- manipulate! do |img|
244
- img.combine_options do |cmd|
245
- cmd.thumbnail "#{width}x#{height}>"
246
- if background == :transparent
247
- cmd.background "rgba(255, 255, 255, 0.0)"
248
- else
249
- cmd.background background
250
- end
251
- cmd.gravity gravity
252
- cmd.extent "#{width}x#{height}"
253
- append_combine_options cmd, combine_options
254
- end
255
- img = yield(img) if block_given?
256
- img
204
+ def resize_and_pad(width, height, background=:transparent, gravity='Center', combine_options: {}, &block)
205
+ width, height = resolve_dimensions(width, height)
206
+
207
+ minimagick!(block) do |builder|
208
+ builder.resize_and_pad(width, height, background: background, gravity: gravity)
209
+ .apply(combine_options)
257
210
  end
258
211
  end
259
212
 
@@ -284,6 +237,9 @@ module CarrierWave
284
237
  # and then pass each of its frames to the supplied block. It will then
285
238
  # save the image to disk.
286
239
  #
240
+ # NOTE: This method exists mostly for backwards compatibility, you should
241
+ # probably use #minimagick!.
242
+ #
287
243
  # === Gotcha
288
244
  #
289
245
  # This method assumes that the object responds to +current_path+.
@@ -303,20 +259,55 @@ module CarrierWave
303
259
  cache_stored_file! if !cached?
304
260
  image = ::MiniMagick::Image.open(current_path)
305
261
 
306
- begin
307
- image.format(@format.to_s.downcase, @page) if @format
308
- image = yield(image)
309
- image.write(current_path)
262
+ image = yield(image)
263
+ FileUtils.mv image.path, current_path
310
264
 
311
- if @format
312
- move_to = current_path.chomp(File.extname(current_path)) + ".#{@format}"
313
- file.content_type = ::MIME::Types.type_for(move_to).first.to_s
314
- file.move_to(move_to, permissions, directory_permissions)
315
- end
265
+ image.run_command("identify", current_path)
266
+ rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e
267
+ message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e)
268
+ raise CarrierWave::ProcessingError, message
269
+ ensure
270
+ image.destroy! if image
271
+ end
316
272
 
317
- image.run_command("identify", current_path)
318
- ensure
319
- image.destroy!
273
+ # Process the image with MiniMagick, using the ImageProcessing gem. This
274
+ # method will build a "convert" ImageMagick command and execute it on the
275
+ # current image.
276
+ #
277
+ # === Gotcha
278
+ #
279
+ # This method assumes that the object responds to +current_path+.
280
+ # Any class that this module is mixed into must have a +current_path+ method.
281
+ # CarrierWave::Uploader does, so you won't need to worry about this in
282
+ # most cases.
283
+ #
284
+ # === Yields
285
+ #
286
+ # [ImageProcessing::Builder] use it to define processing to be performed
287
+ #
288
+ # === Raises
289
+ #
290
+ # [CarrierWave::ProcessingError] if processing failed.
291
+ def minimagick!(block = nil)
292
+ builder = ImageProcessing::MiniMagick.source(current_path)
293
+ builder = yield(builder)
294
+
295
+ result = builder.call
296
+ result.close
297
+
298
+ # backwards compatibility (we want to eventually move away from MiniMagick::Image)
299
+ if block
300
+ image = ::MiniMagick::Image.new(result.path, result)
301
+ image = block.call(image)
302
+ result = image.instance_variable_get(:@tempfile)
303
+ end
304
+
305
+ FileUtils.mv result.path, current_path
306
+
307
+ if File.extname(result.path) != File.extname(current_path)
308
+ move_to = current_path.chomp(File.extname(current_path)) + File.extname(result.path)
309
+ file.content_type = ::MiniMime.lookup_by_filename(move_to).content_type
310
+ file.move_to(move_to, permissions, directory_permissions)
320
311
  end
321
312
  rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e
322
313
  message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e)
@@ -325,21 +316,13 @@ module CarrierWave
325
316
 
326
317
  private
327
318
 
328
- def append_combine_options(cmd, combine_options)
329
- combine_options.each do |method, options|
330
- if options.nil?
331
- cmd.send(method)
332
- else
333
- cmd.send(method, options)
334
- end
319
+ def resolve_dimensions(*dimensions)
320
+ dimensions.map do |value|
321
+ next value unless value.instance_of?(Proc)
322
+ value.arity >= 1 ? value.call(self) : value.call
335
323
  end
336
324
  end
337
325
 
338
- def dimension_from(value)
339
- return value unless value.instance_of?(Proc)
340
- value.arity >= 1 ? value.call(self) : value.call
341
- end
342
-
343
326
  def mini_magick_image
344
327
  ::MiniMagick::Image.read(read)
345
328
  end
@@ -363,7 +363,7 @@ module CarrierWave
363
363
  if options[:format] || @format
364
364
  frames.write("#{options[:format] || @format}:#{current_path}", &write_block)
365
365
  move_to = current_path.chomp(File.extname(current_path)) + ".#{options[:format] || @format}"
366
- file.content_type = ::MIME::Types.type_for(move_to).first.to_s
366
+ file.content_type = ::MiniMime.lookup_by_filename(move_to).content_type
367
367
  file.move_to(move_to, permissions, directory_permissions)
368
368
  else
369
369
  frames.write(current_path, &write_block)