carrierwave 1.3.2 → 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +235 -91
  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 +167 -77
  9. data/lib/carrierwave/orm/activerecord.rb +23 -58
  10. data/lib/carrierwave/processing/mini_magick.rb +108 -123
  11. data/lib/carrierwave/processing/rmagick.rb +11 -15
  12. data/lib/carrierwave/processing/vips.rb +284 -0
  13. data/lib/carrierwave/processing.rb +1 -0
  14. data/lib/carrierwave/sanitized_file.rb +60 -66
  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 +101 -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 +38 -19
  23. data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
  24. data/lib/carrierwave/uploader/content_type_denylist.rb +62 -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 +42 -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 +47 -7
  35. data/lib/carrierwave/uploader/url.rb +7 -4
  36. data/lib/carrierwave/uploader/versions.rb +157 -109
  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 +13 -17
  44. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +3 -3
  45. data/lib/generators/uploader_generator.rb +3 -3
  46. metadata +103 -36
  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,57 @@ 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
75
128
  @remote_urls = urls
76
- @download_error = nil
77
- @integrity_error = nil
78
129
 
79
- @uploaders = urls.zip(remote_request_headers || []).map do |url, header|
80
- uploader = blank_uploader
81
- uploader.download!(url, header || {})
82
- uploader
130
+ clear_unstaged
131
+ @remote_urls.zip(remote_request_headers || []) do |url, header|
132
+ handle_error do
133
+ uploader = blank_uploader
134
+ uploader.download!(url, header || {})
135
+ @uploaders << uploader
136
+ end
83
137
  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)
138
+ write_temporary_identifier
94
139
  end
95
140
 
96
141
  def store!
97
- if remove?
98
- remove!
99
- else
100
- 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
101
155
  end
156
+ @added_uploaders += additions
157
+
158
+ record.write_uploader(serialization_column, identifier)
102
159
  end
103
160
 
104
161
  def urls(*args)
@@ -109,50 +166,50 @@ module CarrierWave
109
166
  uploaders.none?(&:present?)
110
167
  end
111
168
 
169
+ def remove=(value)
170
+ @remove = value
171
+ write_temporary_identifier
172
+ end
173
+
112
174
  def remove?
113
- remove.present? && remove !~ /\A0|false$\z/
175
+ remove.present? && (remove.to_s !~ /\A0|false$\z/)
114
176
  end
115
177
 
116
178
  def remove!
117
- uploaders.reject(&:blank?).each(&:remove!)
179
+ uploaders.each(&:remove!)
180
+ clear!
181
+ end
182
+
183
+ def clear!
184
+ @removed_uploaders += uploaders
185
+ @remove = nil
118
186
  @uploaders = []
119
187
  end
120
188
 
189
+ def reset_changes!
190
+ @removed_uploaders = []
191
+ @added_uploaders = []
192
+ end
193
+
121
194
  def serialization_column
122
195
  option(:mount_on) || column
123
196
  end
124
197
 
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
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!
153
204
  end
154
205
 
155
- 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
156
213
 
157
214
  private
158
215
 
@@ -161,5 +218,38 @@ module CarrierWave
161
218
  self.uploader_options[name] ||= record.class.uploader_option(column, name)
162
219
  end
163
220
 
221
+ def clear_unstaged
222
+ @uploaders ||= []
223
+ staged, unstaged = @uploaders.partition(&:staged)
224
+ @uploaders = staged
225
+ @removed_uploaders += unstaged
226
+ end
227
+
228
+ def handle_error
229
+ yield
230
+ rescue CarrierWave::DownloadError => e
231
+ @download_errors << e
232
+ raise e unless option(:ignore_download_errors)
233
+ rescue CarrierWave::ProcessingError => e
234
+ @processing_errors << e
235
+ raise e unless option(:ignore_processing_errors)
236
+ rescue CarrierWave::IntegrityError => e
237
+ @integrity_errors << e
238
+ raise e unless option(:ignore_integrity_errors)
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
164
254
  end # Mounter
165
255
  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_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)
95
60
  super
96
61
  end
97
62
  RUBY