carrierwave 2.2.1 → 3.0.1

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

Potentially problematic release.


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

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 +27 -13
  5. data/lib/carrierwave/downloader/remote_file.rb +12 -9
  6. data/lib/carrierwave/locale/en.yml +5 -3
  7. data/lib/carrierwave/mount.rb +31 -50
  8. data/lib/carrierwave/mounter.rb +115 -50
  9. data/lib/carrierwave/orm/activerecord.rb +14 -60
  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 +75 -67
  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 +42 -7
  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 +150 -132
  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/uploader_generator.rb +3 -3
  40. metadata +31 -43
  41. /data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +0 -0
@@ -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
33
+
34
+ attr_reader :column, :record, :remote_urls, :remove,
35
+ :integrity_errors, :processing_errors, :download_errors
36
+ attr_accessor :remote_request_headers, :uploader_options
9
37
 
10
- def initialize(record, column, options={})
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,51 @@ 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
+ additions, remains = uploaders.partition(&:cached?)
143
+ existing_paths = (@removed_uploaders + remains).map(&:store_path)
144
+ additions.each do |uploader|
145
+ uploader.deduplicate(existing_paths)
146
+ uploader.store!
147
+ existing_paths << uploader.store_path
148
+ end
149
+ @added_uploaders += additions
150
+ end
151
+
152
+ def write_identifier
153
+ return if record.frozen?
154
+
155
+ clear! if remove?
156
+ record.write_uploader(serialization_column, identifier)
106
157
  end
107
158
 
108
159
  def urls(*args)
@@ -113,52 +164,50 @@ module CarrierWave
113
164
  uploaders.none?(&:present?)
114
165
  end
115
166
 
167
+ def remove=(value)
168
+ @remove = value
169
+ write_temporary_identifier
170
+ end
171
+
116
172
  def remove?
117
- remove.present? && (remove == true || remove !~ /\A0|false$\z/)
173
+ remove.present? && (remove.to_s !~ /\A0|false$\z/)
118
174
  end
119
175
 
120
176
  def remove!
121
- uploaders.reject(&:blank?).each(&:remove!)
122
- @uploaders = []
177
+ uploaders.each(&:remove!)
178
+ clear!
123
179
  end
124
180
 
125
181
  def clear!
182
+ @removed_uploaders += uploaders
183
+ @remove = nil
126
184
  @uploaders = []
127
185
  end
128
186
 
187
+ def reset_changes!
188
+ @removed_uploaders = []
189
+ @added_uploaders = []
190
+ end
191
+
129
192
  def serialization_column
130
193
  option(:mount_on) || column
131
194
  end
132
195
 
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
196
+ def remove_previous
197
+ current_paths = uploaders.map(&:path)
198
+ @removed_uploaders
199
+ .reject {|uploader| current_paths.include?(uploader.path) }
200
+ .each { |uploader| uploader.remove! if uploader.remove_previously_stored_files_after_update }
201
+ reset_changes!
159
202
  end
160
203
 
161
- attr_accessor :uploader_options
204
+ def remove_added
205
+ current_paths = (@removed_uploaders + uploaders.select(&:staged)).map(&:path)
206
+ @added_uploaders
207
+ .reject {|uploader| current_paths.include?(uploader.path) }
208
+ .each { |uploader| uploader.remove! }
209
+ reset_changes!
210
+ end
162
211
 
163
212
  private
164
213
 
@@ -169,7 +218,9 @@ module CarrierWave
169
218
 
170
219
  def clear_unstaged
171
220
  @uploaders ||= []
172
- @uploaders.keep_if(&:staged)
221
+ staged, unstaged = @uploaders.partition(&:staged)
222
+ @uploaders = staged
223
+ @removed_uploaders += unstaged
173
224
  end
174
225
 
175
226
  def handle_error
@@ -184,5 +235,19 @@ module CarrierWave
184
235
  @integrity_errors << e
185
236
  raise e unless option(:ignore_integrity_errors)
186
237
  end
238
+
239
+ def write_temporary_identifier
240
+ return if record.frozen?
241
+
242
+ record.write_uploader(serialization_column, temporary_identifier)
243
+ end
244
+
245
+ def temporary_identifiers
246
+ if remove?
247
+ []
248
+ else
249
+ uploaders.map { |uploader| uploader.temporary_identifier }
250
+ end
251
+ end
187
252
  end # Mounter
188
253
  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,18 @@ 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}"
61
27
  after_commit :"remove_#{column}!", :on => :destroy
62
28
  after_commit :"mark_remove_#{column}_false", :on => :update
29
+
30
+ after_commit :"reset_previous_changes_for_#{column}"
63
31
  after_commit :"remove_previously_stored_#{column}", :on => :update
64
- after_commit :"store_#{column}!", :on => [:create, :update]
32
+ after_rollback :"remove_rolled_back_#{column}"
65
33
 
66
34
  mod = Module.new
67
35
  prepend mod
68
36
  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
37
  # Reset cached mounter on record reload
93
38
  def reload(*)
94
39
  @_mounters = nil
@@ -97,7 +42,16 @@ module CarrierWave
97
42
 
98
43
  # Reset cached mounter on record dup
99
44
  def initialize_dup(other)
100
- @_mounters = nil
45
+ old_uploaders = _mounter(:"#{column}").uploaders
46
+ @_mounters[:"#{column}"] = nil
47
+ # The attribute needs to be cleared to prevent it from picked up as identifier
48
+ write_attribute(:"#{column}", nil)
49
+ super
50
+ _mounter(:"#{column}").cache(old_uploaders)
51
+ end
52
+
53
+ def write_#{column}_identifier
54
+ return unless has_attribute?(_mounter(:#{column}).serialization_column)
101
55
  super
102
56
  end
103
57
  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