carrierwave 2.2.6 → 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +137 -67
  3. data/lib/carrierwave/compatibility/paperclip.rb +4 -2
  4. data/lib/carrierwave/downloader/base.rb +19 -11
  5. data/lib/carrierwave/downloader/remote_file.rb +13 -10
  6. data/lib/carrierwave/locale/en.yml +5 -3
  7. data/lib/carrierwave/mount.rb +36 -50
  8. data/lib/carrierwave/mounter.rb +117 -50
  9. data/lib/carrierwave/orm/activerecord.rb +21 -62
  10. data/lib/carrierwave/processing/mini_magick.rb +15 -13
  11. data/lib/carrierwave/processing/rmagick.rb +11 -15
  12. data/lib/carrierwave/processing/vips.rb +12 -12
  13. data/lib/carrierwave/sanitized_file.rb +49 -77
  14. data/lib/carrierwave/storage/abstract.rb +5 -5
  15. data/lib/carrierwave/storage/file.rb +6 -5
  16. data/lib/carrierwave/storage/fog.rb +74 -66
  17. data/lib/carrierwave/test/matchers.rb +11 -7
  18. data/lib/carrierwave/uploader/cache.rb +18 -10
  19. data/lib/carrierwave/uploader/callbacks.rb +1 -1
  20. data/lib/carrierwave/uploader/configuration.rb +10 -4
  21. data/lib/carrierwave/uploader/{content_type_whitelist.rb → content_type_allowlist.rb} +17 -15
  22. data/lib/carrierwave/uploader/{content_type_blacklist.rb → content_type_denylist.rb} +19 -14
  23. data/lib/carrierwave/uploader/dimension.rb +66 -0
  24. data/lib/carrierwave/uploader/{extension_whitelist.rb → extension_allowlist.rb} +17 -15
  25. data/lib/carrierwave/uploader/{extension_blacklist.rb → extension_denylist.rb} +18 -13
  26. data/lib/carrierwave/uploader/file_size.rb +2 -2
  27. data/lib/carrierwave/uploader/processing.rb +31 -6
  28. data/lib/carrierwave/uploader/proxy.rb +16 -3
  29. data/lib/carrierwave/uploader/store.rb +44 -6
  30. data/lib/carrierwave/uploader/url.rb +1 -1
  31. data/lib/carrierwave/uploader/versions.rb +154 -136
  32. data/lib/carrierwave/uploader.rb +10 -8
  33. data/lib/carrierwave/utilities/file_name.rb +47 -0
  34. data/lib/carrierwave/utilities/uri.rb +14 -11
  35. data/lib/carrierwave/utilities.rb +1 -0
  36. data/lib/carrierwave/validations/active_model.rb +4 -6
  37. data/lib/carrierwave/version.rb +1 -1
  38. data/lib/carrierwave.rb +9 -17
  39. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +1 -1
  40. data/lib/generators/uploader_generator.rb +3 -3
  41. metadata +32 -44
@@ -2,18 +2,49 @@ module CarrierWave
2
2
 
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
- class Mounter #:nodoc:
6
- attr_reader :column, :record, :remote_urls, :integrity_errors,
7
- :processing_errors, :download_errors
8
- attr_accessor :remove, :remote_request_headers
5
+ class Mounter # :nodoc:
6
+ class Single < Mounter # :nodoc
7
+ def identifier
8
+ uploaders.first&.identifier
9
+ end
10
+
11
+ def temporary_identifier
12
+ temporary_identifiers.first
13
+ end
14
+ end
15
+
16
+ class Multiple < Mounter # :nodoc
17
+ def identifier
18
+ uploaders.map(&:identifier).presence
19
+ end
20
+
21
+ def temporary_identifier
22
+ temporary_identifiers.presence
23
+ end
24
+ end
25
+
26
+ def self.build(record, column)
27
+ if record.class.uploader_options[column][:multiple]
28
+ Multiple.new(record, column)
29
+ else
30
+ Single.new(record, column)
31
+ end
32
+ end
9
33
 
10
- def initialize(record, column, options={})
34
+ attr_reader :column, :record, :remote_urls, :remove,
35
+ :integrity_errors, :processing_errors, :download_errors
36
+ attr_accessor :remote_request_headers, :uploader_options
37
+
38
+ def initialize(record, column)
11
39
  @record = record
12
40
  @column = column
13
41
  @options = record.class.uploader_options[column]
14
42
  @download_errors = []
15
43
  @processing_errors = []
16
44
  @integrity_errors = []
45
+
46
+ @removed_uploaders = []
47
+ @added_uploaders = []
17
48
  end
18
49
 
19
50
  def uploader_class
@@ -35,7 +66,7 @@ module CarrierWave
35
66
  def uploaders
36
67
  @uploaders ||= read_identifiers.map do |identifier|
37
68
  uploader = blank_uploader
38
- uploader.retrieve_from_store!(identifier) if identifier.present?
69
+ uploader.retrieve_from_store!(identifier)
39
70
  uploader
40
71
  end
41
72
  end
@@ -46,7 +77,7 @@ module CarrierWave
46
77
  @uploaders = new_files.map do |new_file|
47
78
  handle_error do
48
79
  if new_file.is_a?(String)
49
- if (uploader = old_uploaders.detect { |uploader| uploader.identifier == new_file })
80
+ if (uploader = old_uploaders.detect { |old_uploader| old_uploader.identifier == new_file })
50
81
  uploader.staged = true
51
82
  uploader
52
83
  else
@@ -64,7 +95,9 @@ module CarrierWave
64
95
  uploader
65
96
  end
66
97
  end
67
- end.compact
98
+ end.reject(&:blank?)
99
+ @removed_uploaders += (old_uploaders - @uploaders)
100
+ write_temporary_identifier
68
101
  end
69
102
 
70
103
  def cache_names
@@ -76,33 +109,53 @@ module CarrierWave
76
109
  return if cache_names.blank?
77
110
  clear_unstaged
78
111
  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
112
+ uploader = blank_uploader
113
+ uploader.retrieve_from_cache!(cache_name)
114
+ @uploaders << uploader
115
+ rescue CarrierWave::InvalidParameter
116
+ # ignore
86
117
  end
118
+ write_temporary_identifier
87
119
  end
88
120
 
89
121
  def remote_urls=(urls)
90
- return if urls.blank? || urls.all?(&:blank?)
91
-
122
+ if urls.nil?
123
+ urls = []
124
+ else
125
+ urls = Array.wrap(urls).reject(&:blank?)
126
+ return if urls.blank?
127
+ end
92
128
  @remote_urls = urls
93
129
 
94
130
  clear_unstaged
95
- urls.zip(remote_request_headers || []).each do |url, header|
131
+ @remote_urls.zip(remote_request_headers || []) do |url, header|
96
132
  handle_error do
97
133
  uploader = blank_uploader
98
134
  uploader.download!(url, header || {})
99
135
  @uploaders << uploader
100
136
  end
101
137
  end
138
+ write_temporary_identifier
102
139
  end
103
140
 
104
141
  def store!
105
- uploaders.reject(&:blank?).each(&:store!)
142
+ uploaders.each(&:store!)
143
+ end
144
+
145
+ def write_identifier
146
+ return if record.frozen?
147
+
148
+ clear! if remove?
149
+
150
+ additions, remains = uploaders.partition(&:cached?)
151
+ existing_identifiers = (@removed_uploaders + remains).map(&:identifier)
152
+ additions.each do |uploader|
153
+ uploader.deduplicate(existing_identifiers)
154
+ existing_identifiers << uploader.identifier
155
+ end
156
+ @added_uploaders += additions
157
+
158
+ record.write_uploader(serialization_column, identifier)
106
159
  end
107
160
 
108
161
  def urls(*args)
@@ -113,52 +166,50 @@ module CarrierWave
113
166
  uploaders.none?(&:present?)
114
167
  end
115
168
 
169
+ def remove=(value)
170
+ @remove = value
171
+ write_temporary_identifier
172
+ end
173
+
116
174
  def remove?
117
- remove.present? && (remove == true || remove !~ /\A0|false$\z/)
175
+ remove.present? && (remove.to_s !~ /\A0|false$\z/)
118
176
  end
119
177
 
120
178
  def remove!
121
- uploaders.reject(&:blank?).each(&:remove!)
122
- @uploaders = []
179
+ uploaders.each(&:remove!)
180
+ clear!
123
181
  end
124
182
 
125
183
  def clear!
184
+ @removed_uploaders += uploaders
185
+ @remove = nil
126
186
  @uploaders = []
127
187
  end
128
188
 
189
+ def reset_changes!
190
+ @removed_uploaders = []
191
+ @added_uploaders = []
192
+ end
193
+
129
194
  def serialization_column
130
195
  option(:mount_on) || column
131
196
  end
132
197
 
133
- def remove_previous(before=nil, after=nil)
134
- after ||= []
135
- return unless before
136
-
137
- # both 'before' and 'after' can be string when 'mount_on' option is set
138
- before = before.reject(&:blank?).map do |value|
139
- if value.is_a?(String)
140
- uploader = blank_uploader
141
- uploader.retrieve_from_store!(value)
142
- uploader
143
- else
144
- value
145
- end
146
- end
147
- after_paths = after.reject(&:blank?).map do |value|
148
- if value.is_a?(String)
149
- uploader = blank_uploader
150
- uploader.retrieve_from_store!(value)
151
- uploader
152
- else
153
- value
154
- end.path
155
- end
156
- before.each do |uploader|
157
- uploader.remove! if uploader.remove_previously_stored_files_after_update && !after_paths.include?(uploader.path)
158
- end
198
+ def remove_previous
199
+ current_paths = uploaders.map(&:path)
200
+ @removed_uploaders
201
+ .reject {|uploader| current_paths.include?(uploader.path) }
202
+ .each { |uploader| uploader.remove! if uploader.remove_previously_stored_files_after_update }
203
+ reset_changes!
159
204
  end
160
205
 
161
- attr_accessor :uploader_options
206
+ def remove_added
207
+ current_paths = (@removed_uploaders + uploaders.select(&:staged)).map(&:path)
208
+ @added_uploaders
209
+ .reject {|uploader| current_paths.include?(uploader.path) }
210
+ .each { |uploader| uploader.remove! }
211
+ reset_changes!
212
+ end
162
213
 
163
214
  private
164
215
 
@@ -169,7 +220,9 @@ module CarrierWave
169
220
 
170
221
  def clear_unstaged
171
222
  @uploaders ||= []
172
- @uploaders.keep_if(&:staged)
223
+ staged, unstaged = @uploaders.partition(&:staged)
224
+ @uploaders = staged
225
+ @removed_uploaders += unstaged
173
226
  end
174
227
 
175
228
  def handle_error
@@ -184,5 +237,19 @@ module CarrierWave
184
237
  @integrity_errors << e
185
238
  raise e unless option(:ignore_integrity_errors)
186
239
  end
240
+
241
+ def write_temporary_identifier
242
+ return if record.frozen?
243
+
244
+ record.write_uploader(serialization_column, temporary_identifier)
245
+ end
246
+
247
+ def temporary_identifiers
248
+ if remove?
249
+ []
250
+ else
251
+ uploaders.map { |uploader| uploader.temporary_identifier }
252
+ end
253
+ end
187
254
  end # Mounter
188
255
  end # CarrierWave
@@ -6,40 +6,6 @@ module CarrierWave
6
6
 
7
7
  include CarrierWave::Mount
8
8
 
9
- ##
10
- # See +CarrierWave::Mount#mount_uploader+ for documentation
11
- #
12
- def mount_uploader(column, uploader=nil, options={}, &block)
13
- super
14
-
15
- mod = Module.new
16
- prepend mod
17
- mod.class_eval <<-RUBY, __FILE__, __LINE__+1
18
- def remote_#{column}_url=(url)
19
- column = _mounter(:#{column}).serialization_column
20
- __send__(:"\#{column}_will_change!")
21
- super
22
- end
23
- RUBY
24
- end
25
-
26
- ##
27
- # See +CarrierWave::Mount#mount_uploaders+ for documentation
28
- #
29
- def mount_uploaders(column, uploader=nil, options={}, &block)
30
- super
31
-
32
- mod = Module.new
33
- prepend mod
34
- mod.class_eval <<-RUBY, __FILE__, __LINE__+1
35
- def remote_#{column}_urls=(url)
36
- column = _mounter(:#{column}).serialization_column
37
- __send__(:"\#{column}_will_change!")
38
- super
39
- end
40
- RUBY
41
- end
42
-
43
9
  private
44
10
 
45
11
  def mount_base(column, uploader=nil, options={}, &block)
@@ -56,39 +22,23 @@ module CarrierWave
56
22
  validates_processing_of column if uploader_option(column.to_sym, :validate_processing)
57
23
  validates_download_of column if uploader_option(column.to_sym, :validate_download)
58
24
 
25
+ after_save :"store_#{column}!"
59
26
  before_save :"write_#{column}_identifier"
60
- after_save :"store_previous_changes_for_#{column}"
27
+ if ::ActiveRecord.try(:run_after_transaction_callbacks_in_order_defined)
28
+ after_commit :"remove_previously_stored_#{column}", :on => :update
29
+ after_commit :"reset_previous_changes_for_#{column}"
30
+ after_commit :"mark_remove_#{column}_false", :on => :update
31
+ else
32
+ after_commit :"mark_remove_#{column}_false", :on => :update
33
+ after_commit :"reset_previous_changes_for_#{column}"
34
+ after_commit :"remove_previously_stored_#{column}", :on => :update
35
+ end
61
36
  after_commit :"remove_#{column}!", :on => :destroy
62
- after_commit :"mark_remove_#{column}_false", :on => :update
63
- after_commit :"remove_previously_stored_#{column}", :on => :update
64
- after_commit :"store_#{column}!", :on => [:create, :update]
37
+ after_rollback :"remove_rolled_back_#{column}"
65
38
 
66
39
  mod = Module.new
67
40
  prepend mod
68
41
  mod.class_eval <<-RUBY, __FILE__, __LINE__+1
69
- def #{column}=(new_file)
70
- column = _mounter(:#{column}).serialization_column
71
- if !(new_file.blank? && __send__(:#{column}).blank?)
72
- __send__(:"\#{column}_will_change!")
73
- end
74
-
75
- super
76
- end
77
-
78
- def remove_#{column}=(value)
79
- column = _mounter(:#{column}).serialization_column
80
- result = super
81
- __send__(:"\#{column}_will_change!") if _mounter(:#{column}).remove?
82
- result
83
- end
84
-
85
- def remove_#{column}!
86
- self.remove_#{column} = true
87
- write_#{column}_identifier
88
- self.remove_#{column} = false
89
- super
90
- end
91
-
92
42
  # Reset cached mounter on record reload
93
43
  def reload(*)
94
44
  @_mounters = nil
@@ -97,7 +47,16 @@ module CarrierWave
97
47
 
98
48
  # Reset cached mounter on record dup
99
49
  def initialize_dup(other)
100
- @_mounters = nil
50
+ old_uploaders = _mounter(:"#{column}").uploaders
51
+ super
52
+ @_mounters[:"#{column}"] = nil
53
+ # The attribute needs to be cleared to prevent it from picked up as identifier
54
+ write_attribute(_mounter(:#{column}).serialization_column, nil)
55
+ _mounter(:"#{column}").cache(old_uploaders)
56
+ end
57
+
58
+ def write_#{column}_identifier
59
+ return unless has_attribute?(_mounter(:#{column}).serialization_column)
101
60
  super
102
61
  end
103
62
  RUBY
@@ -88,7 +88,7 @@ module CarrierWave
88
88
  #
89
89
  # === Parameters
90
90
  #
91
- # [format (#to_s)] an abreviation of the format
91
+ # [format (#to_s)] an abbreviation of the format
92
92
  #
93
93
  # === Yields
94
94
  #
@@ -264,7 +264,8 @@ module CarrierWave
264
264
 
265
265
  image.run_command("identify", current_path)
266
266
  rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e
267
- message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e)
267
+ raise e if e.message =~ /(You must have .+ installed|is not installed|executable not found)/
268
+ message = I18n.translate(:"errors.messages.processing_error")
268
269
  raise CarrierWave::ProcessingError, message
269
270
  ensure
270
271
  image.destroy! if image
@@ -306,26 +307,27 @@ module CarrierWave
306
307
 
307
308
  if File.extname(result.path) != File.extname(current_path)
308
309
  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.content_type = Marcel::Magic.by_path(move_to).try(:type)
310
311
  file.move_to(move_to, permissions, directory_permissions)
311
312
  end
312
313
  rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e
313
- message = I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e)
314
+ raise e if e.message =~ /(You must have .+ installed|is not installed|executable not found)/
315
+ message = I18n.translate(:"errors.messages.processing_error")
314
316
  raise CarrierWave::ProcessingError, message
315
317
  end
316
318
 
317
- private
319
+ private
318
320
 
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
323
- end
321
+ def resolve_dimensions(*dimensions)
322
+ dimensions.map do |value|
323
+ next value unless value.instance_of?(Proc)
324
+ value.arity >= 1 ? value.call(self) : value.call
324
325
  end
326
+ end
325
327
 
326
- def mini_magick_image
327
- ::MiniMagick::Image.read(read)
328
- end
328
+ def mini_magick_image
329
+ ::MiniMagick::Image.read(read)
330
+ end
329
331
 
330
332
  end # MiniMagick
331
333
  end # CarrierWave
@@ -62,10 +62,12 @@ module CarrierWave
62
62
  begin
63
63
  require "rmagick"
64
64
  rescue LoadError
65
- require "RMagick"
66
- rescue LoadError => e
67
- e.message << " (You may need to install the rmagick gem)"
68
- raise e
65
+ begin
66
+ require "RMagick"
67
+ rescue LoadError => e
68
+ e.message << " (You may need to install the rmagick gem)"
69
+ raise e
70
+ end
69
71
  end
70
72
 
71
73
  prepend Module.new {
@@ -109,7 +111,7 @@ module CarrierWave
109
111
  #
110
112
  # === Parameters
111
113
  #
112
- # [format (#to_s)] an abreviation of the format
114
+ # [format (#to_s)] an abbreviation of the format
113
115
  #
114
116
  # === Yields
115
117
  #
@@ -228,13 +230,7 @@ module CarrierWave
228
230
  height = dimension_from height
229
231
  manipulate! do |img|
230
232
  img.resize_to_fit!(width, height)
231
- new_img = ::Magick::Image.new(width, height) { |img| img.background_color = background == :transparent ? 'rgba(255,255,255,0)' : background.to_s }
232
- if background == :transparent
233
- filled = new_img.matte_floodfill(1, 1)
234
- else
235
- filled = new_img.color_floodfill(1, 1, ::Magick::Pixel.from_color(background))
236
- end
237
- destroy_image(new_img)
233
+ filled = ::Magick::Image.new(width, height) { |image| image.background_color = background == :transparent ? 'rgba(255,255,255,0)' : background.to_s }
238
234
  filled.composite!(img, gravity, ::Magick::OverCompositeOp)
239
235
  destroy_image(img)
240
236
  filled = yield(filled) if block_given?
@@ -363,15 +359,15 @@ module CarrierWave
363
359
  if options[:format] || @format
364
360
  frames.write("#{options[:format] || @format}:#{current_path}", &write_block)
365
361
  move_to = current_path.chomp(File.extname(current_path)) + ".#{options[:format] || @format}"
366
- file.content_type = ::MiniMime.lookup_by_filename(move_to).content_type
362
+ file.content_type = Marcel::Magic.by_path(move_to).try(:type)
367
363
  file.move_to(move_to, permissions, directory_permissions)
368
364
  else
369
365
  frames.write(current_path, &write_block)
370
366
  end
371
367
 
372
368
  destroy_image(frames)
373
- rescue ::Magick::ImageMagickError => e
374
- raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.rmagick_processing_error", :e => e)
369
+ rescue ::Magick::ImageMagickError
370
+ raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.processing_error")
375
371
  end
376
372
 
377
373
  private
@@ -259,26 +259,26 @@ module CarrierWave
259
259
 
260
260
  if File.extname(result.path) != File.extname(current_path)
261
261
  move_to = current_path.chomp(File.extname(current_path)) + File.extname(result.path)
262
- file.content_type = ::MiniMime.lookup_by_filename(move_to).content_type
262
+ file.content_type = Marcel::Magic.by_path(move_to).try(:type)
263
263
  file.move_to(move_to, permissions, directory_permissions)
264
264
  end
265
- rescue ::Vips::Error => e
266
- message = I18n.translate(:"errors.messages.vips_processing_error", :e => e)
265
+ rescue ::Vips::Error
266
+ message = I18n.translate(:"errors.messages.processing_error")
267
267
  raise CarrierWave::ProcessingError, message
268
268
  end
269
269
 
270
- private
270
+ private
271
271
 
272
- def resolve_dimensions(*dimensions)
273
- dimensions.map do |value|
274
- next value unless value.instance_of?(Proc)
275
- value.arity >= 1 ? value.call(self) : value.call
276
- end
272
+ def resolve_dimensions(*dimensions)
273
+ dimensions.map do |value|
274
+ next value unless value.instance_of?(Proc)
275
+ value.arity >= 1 ? value.call(self) : value.call
277
276
  end
277
+ end
278
278
 
279
- def vips_image
280
- ::Vips::Image.new_from_buffer(read, "")
281
- end
279
+ def vips_image
280
+ ::Vips::Image.new_from_buffer(read, "")
281
+ end
282
282
 
283
283
  end # Vips
284
284
  end # CarrierWave