carrierwave 1.3.4 → 3.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +278 -86
  3. data/lib/carrierwave/compatibility/paperclip.rb +4 -2
  4. data/lib/carrierwave/downloader/base.rb +101 -0
  5. data/lib/carrierwave/downloader/remote_file.rb +68 -0
  6. data/lib/carrierwave/locale/en.yml +9 -6
  7. data/lib/carrierwave/mount.rb +53 -61
  8. data/lib/carrierwave/mounter.rb +169 -77
  9. data/lib/carrierwave/orm/activerecord.rb +23 -58
  10. data/lib/carrierwave/processing/mini_magick.rb +137 -123
  11. data/lib/carrierwave/processing/rmagick.rb +47 -20
  12. data/lib/carrierwave/processing/vips.rb +315 -0
  13. data/lib/carrierwave/processing.rb +1 -0
  14. data/lib/carrierwave/sanitized_file.rb +91 -70
  15. data/lib/carrierwave/storage/abstract.rb +5 -5
  16. data/lib/carrierwave/storage/file.rb +6 -5
  17. data/lib/carrierwave/storage/fog.rb +114 -62
  18. data/lib/carrierwave/storage.rb +1 -0
  19. data/lib/carrierwave/test/matchers.rb +11 -7
  20. data/lib/carrierwave/uploader/cache.rb +40 -24
  21. data/lib/carrierwave/uploader/callbacks.rb +1 -1
  22. data/lib/carrierwave/uploader/configuration.rb +43 -20
  23. data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
  24. data/lib/carrierwave/uploader/content_type_denylist.rb +65 -0
  25. data/lib/carrierwave/uploader/dimension.rb +66 -0
  26. data/lib/carrierwave/uploader/download.rb +2 -123
  27. data/lib/carrierwave/uploader/extension_allowlist.rb +63 -0
  28. data/lib/carrierwave/uploader/extension_denylist.rb +64 -0
  29. data/lib/carrierwave/uploader/file_size.rb +2 -2
  30. data/lib/carrierwave/uploader/mountable.rb +6 -0
  31. data/lib/carrierwave/uploader/processing.rb +45 -7
  32. data/lib/carrierwave/uploader/proxy.rb +17 -4
  33. data/lib/carrierwave/uploader/serialization.rb +1 -1
  34. data/lib/carrierwave/uploader/store.rb +73 -7
  35. data/lib/carrierwave/uploader/url.rb +7 -4
  36. data/lib/carrierwave/uploader/versions.rb +161 -111
  37. data/lib/carrierwave/uploader.rb +10 -17
  38. data/lib/carrierwave/utilities/file_name.rb +47 -0
  39. data/lib/carrierwave/utilities/uri.rb +14 -11
  40. data/lib/carrierwave/utilities.rb +1 -0
  41. data/lib/carrierwave/validations/active_model.rb +7 -9
  42. data/lib/carrierwave/version.rb +1 -1
  43. data/lib/carrierwave.rb +22 -17
  44. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +13 -4
  45. data/lib/generators/uploader_generator.rb +3 -3
  46. metadata +115 -44
  47. data/lib/carrierwave/uploader/content_type_blacklist.rb +0 -48
  48. data/lib/carrierwave/uploader/content_type_whitelist.rb +0 -48
  49. data/lib/carrierwave/uploader/extension_blacklist.rb +0 -51
  50. data/lib/carrierwave/uploader/extension_whitelist.rb +0 -52
@@ -2,15 +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_error,
7
- :processing_error, :download_error
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]
42
+ @download_errors = []
43
+ @processing_errors = []
44
+ @integrity_errors = []
45
+
46
+ @removed_uploaders = []
47
+ @added_uploaders = []
14
48
  end
15
49
 
16
50
  def uploader_class
@@ -32,27 +66,38 @@ module CarrierWave
32
66
  def uploaders
33
67
  @uploaders ||= read_identifiers.map do |identifier|
34
68
  uploader = blank_uploader
35
- uploader.retrieve_from_store!(identifier) if identifier.present?
69
+ uploader.retrieve_from_store!(identifier)
36
70
  uploader
37
71
  end
38
72
  end
39
73
 
40
74
  def cache(new_files)
41
- return if not new_files or new_files == ""
75
+ return if !new_files.is_a?(Array) && new_files.blank?
76
+ old_uploaders = uploaders
42
77
  @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)
78
+ handle_error do
79
+ if new_file.is_a?(String)
80
+ if (uploader = old_uploaders.detect { |old_uploader| old_uploader.identifier == new_file })
81
+ uploader.staged = true
82
+ uploader
83
+ else
84
+ begin
85
+ uploader = blank_uploader
86
+ uploader.retrieve_from_cache!(new_file)
87
+ uploader
88
+ rescue CarrierWave::InvalidParameter
89
+ nil
90
+ end
91
+ end
92
+ else
93
+ uploader = blank_uploader
94
+ uploader.cache!(new_file)
95
+ uploader
96
+ end
97
+ end
98
+ end.reject(&:blank?)
99
+ @removed_uploaders += (old_uploaders - @uploaders)
100
+ write_temporary_identifier
56
101
  end
57
102
 
58
103
  def cache_names
@@ -60,45 +105,58 @@ module CarrierWave
60
105
  end
61
106
 
62
107
  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|
108
+ cache_names = cache_names.reject(&:blank?)
109
+ return if cache_names.blank?
110
+ clear_unstaged
111
+ cache_names.each do |cache_name|
65
112
  uploader = blank_uploader
66
113
  uploader.retrieve_from_cache!(cache_name)
67
- uploader
114
+ @uploaders << uploader
115
+ rescue CarrierWave::InvalidParameter
116
+ # ignore
68
117
  end
69
- rescue CarrierWave::InvalidParameter
118
+ write_temporary_identifier
70
119
  end
71
120
 
72
121
  def remote_urls=(urls)
73
- return if not urls or urls == "" or urls.all?(&:blank?)
74
-
122
+ if urls.nil?
123
+ urls = []
124
+ else
125
+ urls = Array.wrap(urls).reject(&:blank?)
126
+ return if urls.blank?
127
+ end
128
+ @download_errors.clear
75
129
  @remote_urls = urls
76
- @download_error = nil
77
- @integrity_error = nil
78
130
 
79
- @uploaders = urls.zip(remote_request_headers || []).map do |url, header|
80
- uploader = blank_uploader
81
- uploader.download!(url, header || {})
82
- uploader
131
+ clear_unstaged
132
+ @remote_urls.zip(remote_request_headers || []) do |url, header|
133
+ handle_error do
134
+ uploader = blank_uploader
135
+ uploader.download!(url, header || {})
136
+ @uploaders << uploader
137
+ end
83
138
  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)
139
+ write_temporary_identifier
94
140
  end
95
141
 
96
142
  def store!
97
- if remove?
98
- remove!
99
- else
100
- uploaders.reject(&:blank?).each(&:store!)
143
+ uploaders.each(&:store!)
144
+ end
145
+
146
+ def write_identifier
147
+ return if record.frozen?
148
+
149
+ clear! if remove?
150
+
151
+ additions, remains = uploaders.partition(&:cached?)
152
+ existing_identifiers = (@removed_uploaders + remains).map(&:identifier)
153
+ additions.each do |uploader|
154
+ uploader.deduplicate(existing_identifiers)
155
+ existing_identifiers << uploader.identifier
101
156
  end
157
+ @added_uploaders += additions
158
+
159
+ record.write_uploader(serialization_column, identifier)
102
160
  end
103
161
 
104
162
  def urls(*args)
@@ -109,50 +167,51 @@ module CarrierWave
109
167
  uploaders.none?(&:present?)
110
168
  end
111
169
 
170
+ def remove=(value)
171
+ uploaders # Ensure that uploaders are initialized based on the current value
172
+ @remove = value
173
+ write_temporary_identifier
174
+ end
175
+
112
176
  def remove?
113
- remove.present? && (remove == true || remove !~ /\A0|false$\z/)
177
+ remove.present? && (remove.to_s !~ /\A0|false$\z/)
114
178
  end
115
179
 
116
180
  def remove!
117
- uploaders.reject(&:blank?).each(&:remove!)
181
+ uploaders.each(&:remove!)
182
+ clear!
183
+ end
184
+
185
+ def clear!
186
+ @removed_uploaders += uploaders
187
+ @remove = nil
118
188
  @uploaders = []
119
189
  end
120
190
 
191
+ def reset_changes!
192
+ @removed_uploaders = []
193
+ @added_uploaders = []
194
+ end
195
+
121
196
  def serialization_column
122
197
  option(:mount_on) || column
123
198
  end
124
199
 
125
- def remove_previous(before=nil, after=nil)
126
- after ||= []
127
- return unless before
128
-
129
- # both 'before' and 'after' can be string when 'mount_on' option is set
130
- before = before.reject(&:blank?).map do |value|
131
- if value.is_a?(String)
132
- uploader = blank_uploader
133
- uploader.retrieve_from_store!(value)
134
- uploader
135
- else
136
- value
137
- end
138
- end
139
- after_paths = after.reject(&:blank?).map do |value|
140
- if value.is_a?(String)
141
- uploader = blank_uploader
142
- uploader.retrieve_from_store!(value)
143
- uploader
144
- else
145
- value
146
- end.path
147
- end
148
- 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
152
- end
200
+ def remove_previous
201
+ current_paths = uploaders.map(&:path)
202
+ @removed_uploaders
203
+ .reject {|uploader| current_paths.include?(uploader.path) }
204
+ .each { |uploader| uploader.remove! if uploader.remove_previously_stored_files_after_update }
205
+ reset_changes!
153
206
  end
154
207
 
155
- attr_accessor :uploader_options
208
+ def remove_added
209
+ current_paths = (@removed_uploaders + uploaders.select(&:staged)).map(&:path)
210
+ @added_uploaders
211
+ .reject {|uploader| current_paths.include?(uploader.path) }
212
+ .each { |uploader| uploader.remove! }
213
+ reset_changes!
214
+ end
156
215
 
157
216
  private
158
217
 
@@ -161,5 +220,38 @@ module CarrierWave
161
220
  self.uploader_options[name] ||= record.class.uploader_option(column, name)
162
221
  end
163
222
 
223
+ def clear_unstaged
224
+ uploaders # Ensure that uploaders are initialized based on the current value
225
+ staged, unstaged = @uploaders.partition(&:staged)
226
+ @uploaders = staged
227
+ @removed_uploaders += unstaged
228
+ end
229
+
230
+ def handle_error
231
+ yield
232
+ rescue CarrierWave::DownloadError => e
233
+ @download_errors << e
234
+ raise e unless option(:ignore_download_errors)
235
+ rescue CarrierWave::ProcessingError => e
236
+ @processing_errors << e
237
+ raise e unless option(:ignore_processing_errors)
238
+ rescue CarrierWave::IntegrityError => e
239
+ @integrity_errors << e
240
+ raise e unless option(:ignore_integrity_errors)
241
+ end
242
+
243
+ def write_temporary_identifier
244
+ return if record.frozen?
245
+
246
+ record.write_uploader(serialization_column, temporary_identifier)
247
+ end
248
+
249
+ def temporary_identifiers
250
+ if remove?
251
+ []
252
+ else
253
+ uploaders.map { |uploader| uploader.temporary_identifier }
254
+ end
255
+ end
164
256
  end # Mounter
165
257
  end # CarrierWave
@@ -6,36 +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
- class_eval <<-RUBY, __FILE__, __LINE__+1
16
- def remote_#{column}_url=(url)
17
- column = _mounter(:#{column}).serialization_column
18
- __send__(:"\#{column}_will_change!")
19
- super
20
- end
21
- RUBY
22
- end
23
-
24
- ##
25
- # See +CarrierWave::Mount#mount_uploaders+ for documentation
26
- #
27
- def mount_uploaders(column, uploader=nil, options={}, &block)
28
- super
29
-
30
- class_eval <<-RUBY, __FILE__, __LINE__+1
31
- def remote_#{column}_urls=(url)
32
- column = _mounter(:#{column}).serialization_column
33
- __send__(:"\#{column}_will_change!")
34
- super
35
- end
36
- RUBY
37
- end
38
-
39
9
  private
40
10
 
41
11
  def mount_base(column, uploader=nil, options={}, &block)
@@ -54,35 +24,21 @@ module CarrierWave
54
24
 
55
25
  after_save :"store_#{column}!"
56
26
  before_save :"write_#{column}_identifier"
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
57
36
  after_commit :"remove_#{column}!", :on => :destroy
58
- after_commit :"mark_remove_#{column}_false", :on => :update
59
-
60
- after_save :"store_previous_changes_for_#{column}"
61
- after_commit :"remove_previously_stored_#{column}", :on => :update
62
-
63
- class_eval <<-RUBY, __FILE__, __LINE__+1
64
- def #{column}=(new_file)
65
- column = _mounter(:#{column}).serialization_column
66
- if !(new_file.blank? && __send__(:#{column}).blank?)
67
- __send__(:"\#{column}_will_change!")
68
- end
69
-
70
- super
71
- end
72
-
73
- def remove_#{column}=(value)
74
- column = _mounter(:#{column}).serialization_column
75
- __send__(:"\#{column}_will_change!")
76
- super
77
- end
78
-
79
- def remove_#{column}!
80
- self.remove_#{column} = true
81
- write_#{column}_identifier
82
- self.remove_#{column} = false
83
- super
84
- end
37
+ after_rollback :"remove_rolled_back_#{column}"
85
38
 
39
+ mod = Module.new
40
+ prepend mod
41
+ mod.class_eval <<-RUBY, __FILE__, __LINE__+1
86
42
  # Reset cached mounter on record reload
87
43
  def reload(*)
88
44
  @_mounters = nil
@@ -91,7 +47,16 @@ module CarrierWave
91
47
 
92
48
  # Reset cached mounter on record dup
93
49
  def initialize_dup(other)
94
- @_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_uploader(_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)
95
60
  super
96
61
  end
97
62
  RUBY