carrierwave 3.1.4 → 4.0.0.rc

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3fce89671ca96c66db403199f1eab005bdb208ae699bcd6c6d58d470ec337d96
4
- data.tar.gz: 0047624b2e05641fbb2d65d97815726e9bc296fc8cdb53c37815ead2ce30d6e4
3
+ metadata.gz: 663f843bb0d228ae33a1afa9bf60c466ba068f7f13e5bd8cfa505de59029a34d
4
+ data.tar.gz: d6cdd19287a021a50268c48e2d4be4acf9bf1ed14680ff12c83c29226d7a7652
5
5
  SHA512:
6
- metadata.gz: 5f63f1e37da1f107660263cc2d4be4e09dd0076e12bf80bc057449ddbd288887210ae17392d4092ff240c9992be3819721e7665a72023e4b58a8e9d351434d37
7
- data.tar.gz: 205220c3605f50f23379781d94c587aee68a75aa7d4d854a258d362983122115641482098e81574dd873acce90deeb124d01b24c9ff7970dc45c2cb3146a9bd1
6
+ metadata.gz: 4eee63b300dfe7ce3d4f2061bb8681b0c23c6b434ab45fe19e015a1cb6a8bfd19c7a84a30f0454c84d23df843ce51e71498cde0b4fef671f941d94ee8e1160f5
7
+ data.tar.gz: e77c6b1e21ecf67d074b8fc893b4d762c3fca35fe69014bb9504bb98992ca49858ee8ad060b9e8905d897e18dd9169be0ef12ad6f640e4a02abd4e854f414e14
data/README.md CHANGED
@@ -35,6 +35,23 @@ gem 'carrierwave', '~> 3.0'
35
35
 
36
36
  Finally, restart the server to apply the changes.
37
37
 
38
+ ### Supported Ruby and Rails versions
39
+
40
+ Each release is tested against the versions below. The gemspec only states a
41
+ minimum, so Bundler will happily install an old CarrierWave alongside a Rails
42
+ newer than it has ever seen — which is how a patch-level upgrade ends up failing
43
+ at boot. Check this table when upgrading Rails.
44
+
45
+ | CarrierWave | Rails | Ruby |
46
+ |-------------|------------|----------|
47
+ | 4.0 | 7.0 – 8.1 | >= 2.7 |
48
+ | 3.1 | 6.0 – 8.0 | >= 2.5 |
49
+ | 3.0 | 6.0 – 7.1 | >= 2.5 |
50
+ | 2.2 | 5.0 – 6.1 | >= 2.2.2 |
51
+
52
+ Only the latest release gets fixes; older lines are listed so you can tell
53
+ whether your combination was ever supported.
54
+
38
55
  ## Upgrading from 2.x or earlier
39
56
 
40
57
  CarrierWave 3.0 comes with a change in the way of handling the file extension on conversion. This results in following issues if you use `process convert: :format` to change the file format:
@@ -159,6 +176,14 @@ example, create a migration like this:
159
176
 
160
177
  __Note__: JSON datatype doesn't exists in SQLite adapter, that's why you can use a string datatype which will be serialized in model.
161
178
 
179
+ Leave the column nullable: `NULL` is what it holds when no file is mounted, so a `default: []` or `null: false` on it doesn't hold — the `NULL` is written on save. The uploader's own readers answer an empty array either way, and are what to read:
180
+
181
+ ```ruby
182
+ user.avatars # => [] when there is no file
183
+ user.avatars_identifiers # => []
184
+ user[:avatars] # => nil, the column as it is stored
185
+ ```
186
+
162
187
  Open your model file and mount the uploader:
163
188
 
164
189
 
@@ -223,7 +248,7 @@ end
223
248
  This works for the file storage as well as Amazon S3 and Rackspace Cloud Files.
224
249
  Define `store_dir` as `nil` if you'd like to store files at the root level.
225
250
 
226
- If you store files outside the project root folder, you may want to define `cache_dir` in the same way:
251
+ With the `:file` storage, if you store files outside the project root folder, you may want to define `cache_dir` in the same way:
227
252
 
228
253
  ```ruby
229
254
  class MyUploader < CarrierWave::Uploader::Base
@@ -245,6 +270,14 @@ class MyUploader < CarrierWave::Uploader::Base
245
270
  end
246
271
  ```
247
272
 
273
+ The name is worked out again whenever the stored file is located, so it has to come out the same every time. Memoise anything that would otherwise differ per call, in an instance variable of your own, as `@filename` is the uploader's:
274
+
275
+ ```ruby
276
+ def filename
277
+ @stable_filename ||= "#{SecureRandom.uuid}.#{file.extension}"
278
+ end
279
+ ```
280
+
248
281
  Some old documentations (like [this](https://stackoverflow.com/a/5865117)) may instruct you to safeguard the filename value with `if original_filename`, but it's no longer necessary with CarrierWave 3.0 or later.
249
282
 
250
283
  ## Securing uploads
@@ -378,6 +411,32 @@ class MyUploader < CarrierWave::Uploader::Base
378
411
  end
379
412
  ```
380
413
 
414
+ `process convert: format` cannot be given a condition. The file extension follows the
415
+ conversion, and it has to be worked out again when the file is retrieved, where whether
416
+ the condition held is not known. Convert unconditionally, or give the format a version
417
+ of its own:
418
+
419
+ ```ruby
420
+ version :webp, if: :convert_to_webp? do
421
+ process convert: :webp # unconditional within the version
422
+ end
423
+ ```
424
+
425
+ If you need to convert on a condition and keep it on the uploader itself, take the
426
+ naming over so that it still follows from the identifier:
427
+
428
+ ```ruby
429
+ process :to_jpeg, if: :heic?
430
+
431
+ def to_jpeg
432
+ minimagick! { |builder| builder.convert('jpg') }
433
+ end
434
+
435
+ def full_filename(for_file)
436
+ for_file.sub(/\.heic\z/i, '.jpg')
437
+ end
438
+ ```
439
+
381
440
  ### Nested versions
382
441
 
383
442
  It is possible to nest versions within versions:
@@ -424,6 +483,13 @@ end
424
483
  ```
425
484
 
426
485
  The `model` variable points to the instance object the uploader is attached to.
486
+ The condition is evaluated when the version is accessed, so it isn't called at all
487
+ as long as you only use the original file.
488
+
489
+ Note that `present?` on a version only tells whether a file is assigned, not whether
490
+ that version was actually created. Use `uploader.thumb.exists?` to ask the storage,
491
+ which costs a request when the storage is a remote one, or
492
+ [record what was stored](#recording-what-was-stored) to have the answer at hand.
427
493
 
428
494
  ### Create versions from existing versions
429
495
 
@@ -466,7 +532,44 @@ end
466
532
 
467
533
  Please note that `#full_filename` mustn't be constructed based on a dynamic value
468
534
  that can change from the time of store and time of retrieval, since it will result in
469
- being unable to retrieve a file previously stored.
535
+ being unable to retrieve a file previously stored. Storing refuses to leave a file
536
+ somewhere a later request would not look, so this is caught rather than silent.
537
+
538
+ ## Recording what was stored
539
+
540
+ By default CarrierWave persists the identifier alone, and works out everything else on
541
+ retrieval by re-running the uploader's definition and asking the storage. Which versions
542
+ a conditional creates is decided when the file is stored, so re-deciding it later can
543
+ give a different answer than what is actually there, and asking a remote storage for
544
+ things like the size costs a request per record.
545
+
546
+ Give the mount a column to record it in, and it stops guessing:
547
+
548
+ ```ruby
549
+ class Event < ActiveRecord::Base
550
+ mount_uploader :image, ImageUploader, metadata_column: :image_metadata
551
+ end
552
+ ```
553
+
554
+ ```ruby
555
+ add_column :events, :image_metadata, :json
556
+ ```
557
+
558
+ The column can be a `json`/`jsonb` one, a serialized one, or plain text holding JSON.
559
+ For `mount_uploaders` it holds one set of facts per file.
560
+
561
+ Records stored before the column was added have nothing recorded, and keep working as
562
+ they always have, so no backfill is needed.
563
+
564
+ Override `#build_metadata` to record your own facts:
565
+
566
+ ```ruby
567
+ class ImageUploader < CarrierWave::Uploader::Base
568
+ def build_metadata
569
+ super.merge('width' => width, 'height' => height)
570
+ end
571
+ end
572
+ ```
470
573
 
471
574
  ## Making uploads work across form redisplays
472
575
 
@@ -500,6 +603,11 @@ case of images, a small thumbnail would be a good indicator:
500
603
  <% end %>
501
604
  ```
502
605
 
606
+ When the cache storage is a remote one, uploading the cached file to it is deferred until
607
+ it turns out to be needed beyond the current request, which reading `avatar_cache` or
608
+ `avatar_url` tells CarrierWave. If you carry the cache name over by other means, call
609
+ `user.avatar.materialize_cache!` before handing it out.
610
+
503
611
  ## Removing uploaded files
504
612
 
505
613
  If you want to remove a previously uploaded file on a mounted uploader, you can
@@ -562,6 +670,57 @@ CarrierWave.configure do |config|
562
670
  end
563
671
  ```
564
672
 
673
+ ## Uploading directly to the storage
674
+
675
+ When the file is stored on a cloud service, it can be uploaded straight to it,
676
+ without the bytes passing through your application. `#direct_upload` tells the
677
+ client where to put the file, and under which name to hand it back afterwards:
678
+
679
+ ```ruby
680
+ class UploadsController < ApplicationController
681
+ def create
682
+ upload = User.new.avatar.direct_upload(filename: params[:filename])
683
+ render json: upload.to_h
684
+ # => { url: "https://mybucket.s3.amazonaws.com/uploads/tmp/1758…-4829/cat.jpg?X-Amz-…",
685
+ # method: "PUT", headers: {}, cache_name: "1758…-4829/cat.jpg" }
686
+ end
687
+ end
688
+ ```
689
+
690
+ The file goes where the cache expects it, so the upload is finished by assigning
691
+ the returned name to the `avatar_cache` field you already have for form
692
+ redisplays. In the browser:
693
+
694
+ ```js
695
+ const upload = await (await fetch(`/uploads?filename=${encodeURIComponent(file.name)}`)).json();
696
+ await fetch(upload.url, { method: upload.method, headers: upload.headers, body: file });
697
+ document.querySelector('#user_avatar_cache').value = upload.cache_name;
698
+ // then submit the form as usual
699
+ ```
700
+
701
+ The file is stored where it belongs when the record is saved, as any cached file
702
+ is. Libraries which upload for you take the same three values: with
703
+ [Uppy](https://uppy.io/docs/aws-s3/), return `upload.to_h` from
704
+ `getUploadParameters`.
705
+
706
+ The cache has to be on the cloud service for this, which it is when `storage` is
707
+ set to `:fog` and `cache_storage` is left alone. A few more things to know:
708
+
709
+ * the filename is sanitized before it is signed into the URL, so the name the
710
+ file is stored under is the one in `cache_name`, not the one you passed;
711
+ * the upload stays possible for `fog_authenticated_url_expiration` seconds, which
712
+ `expires_in:` overrides;
713
+ * the size cannot be limited by the URL. Check it in the client and again with
714
+ `size_range` when the file comes back, and let `clean_cached_files!` or a
715
+ lifecycle rule on the cache directory collect what was uploaded and never used;
716
+ * pass `content_type:` to fix the type the client has to send. It is signed into
717
+ the URL and returned in `headers`, so the client has to send exactly that;
718
+ * uploaders which process what they store, or have versions, refuse to hand out
719
+ an upload: neither can be done to a file the application never sees.
720
+
721
+ Your storage service has to allow the upload to be made from the browser, which
722
+ for S3 means a CORS rule allowing `PUT` from your origin.
723
+
565
724
  ## Providing a default URL
566
725
 
567
726
  In many cases, especially when working with images, it might be a good idea to
@@ -946,6 +1105,10 @@ end
946
1105
  If you're uploading images, you'll probably want to manipulate them in some way,
947
1106
  you might want to create thumbnail images for example.
948
1107
 
1108
+ The processing library itself is not a dependency of CarrierWave, so add the one
1109
+ your uploaders use to your own Gemfile — `mini_magick`, `ruby-vips` or `rmagick`
1110
+ — along with the tool it drives (ImageMagick or libvips).
1111
+
949
1112
  ### Using MiniMagick
950
1113
 
951
1114
  MiniMagick performs all the operations using the 'convert' CLI which is part of the standard ImageMagick kit.
@@ -1024,10 +1187,24 @@ class ImageFileUploader < CarrierWave::Uploader::Base
1024
1187
  end
1025
1188
  ````
1026
1189
 
1190
+ For anything the methods below don't cover, `vips!` yields the [ImageProcessing](https://github.com/janko/image_processing/blob/master/doc/vips.md) builder (`minimagick!` does the same for MiniMagick). For example, to strip the metadata and set the quality:
1191
+
1192
+ ````ruby
1193
+ class ImageFileUploader < CarrierWave::Uploader::Base
1194
+ include CarrierWave::Vips
1195
+
1196
+ process :optimize
1197
+
1198
+ def optimize
1199
+ vips! { |builder| builder.saver(strip: true, quality: 90) }
1200
+ end
1201
+ end
1202
+ ````
1203
+
1027
1204
  ### List of available processing methods:
1028
1205
 
1029
1206
  > [!NOTE]
1030
- > While the intention is to provide uniform interfaces to all three processing libraries the availability and implementation of processing methods can <a href="supported-processing-methods">vary slightly between them</a>.
1207
+ > While the intention is to provide uniform interfaces to all three processing libraries the availability and implementation of processing methods can <a href="#supported-processing-methods">vary slightly between them</a>.
1031
1208
 
1032
1209
  - `convert` - Changes the image encoding format to the given format (eg. jpg). This operation is treated specially to trigger the change of the file extension, so it matches with the format of the resulting file.
1033
1210
  - `resize_to_limit` - Resize the image to fit within the specified dimensions while retaining the original aspect ratio. Will only resize the image if it is larger than the specified dimensions. The resulting image may be shorter or narrower than specified in the smaller dimension but will not be larger than the specified values.
@@ -0,0 +1,20 @@
1
+ module CarrierWave
2
+ ##
3
+ # What a client needs to upload a file straight into the cache. See
4
+ # CarrierWave::Uploader::DirectUpload#direct_upload.
5
+ #
6
+ class DirectUpload
7
+ attr_reader :url, :method, :headers, :cache_name
8
+
9
+ def initialize(url:, cache_name:, headers: {}, method: 'PUT')
10
+ @url = url
11
+ @cache_name = cache_name
12
+ @headers = headers
13
+ @method = method
14
+ end
15
+
16
+ def to_h
17
+ { url: url, method: method, headers: headers, cache_name: cache_name }
18
+ end
19
+ end # DirectUpload
20
+ end # CarrierWave
@@ -1,6 +1,6 @@
1
1
  require 'open-uri'
2
2
  require 'ssrf_filter'
3
- require 'addressable'
3
+ require 'addressable/uri'
4
4
  require 'carrierwave/downloader/remote_file'
5
5
 
6
6
  module CarrierWave
@@ -8,6 +8,14 @@ module CarrierWave
8
8
  class Base
9
9
  include CarrierWave::Utilities::Uri
10
10
 
11
+ # Leftovers from pasting, removed before parsing as in the first steps of
12
+ # https://url.spec.whatwg.org/#concept-basic-url-parser
13
+ LEADING_TRAILING_JUNK = /\A[\x00-\x20]+|[\x00-\x20]+\z/.freeze
14
+ EMBEDDED_JUNK = /[\t\n\r]/.freeze
15
+ # Matches a host name with an optional port, but not a scheme like 'http://' or 'data:'
16
+ HOSTISH = /\A[[:alnum:]][[:alnum:]\-._]*(?::\d+)?(?:[\/?#]|\z)/.freeze
17
+ DEFAULT_SCHEME = 'https'.freeze
18
+
11
19
  attr_reader :uploader
12
20
 
13
21
  def initialize(uploader)
@@ -29,15 +37,16 @@ module CarrierWave
29
37
  uri = process_uri(url.to_s)
30
38
  begin
31
39
  if skip_ssrf_protection?(uri)
32
- response = OpenURI.open_uri(process_uri(url.to_s), headers)
40
+ response = OpenURI.open_uri(uri, headers)
33
41
  else
34
42
  request = nil
43
+ ssrf_options = @uploader.ssrf_filter_options || {}
35
44
  if ::SsrfFilter::VERSION.to_f < 1.1
36
- response = SsrfFilter.get(uri, headers: headers) do |req|
45
+ response = SsrfFilter.get(uri, headers: headers, **ssrf_options) do |req|
37
46
  request = req
38
47
  end
39
48
  else
40
- response = SsrfFilter.get(uri, headers: headers, request_proc: ->(req) { request = req }) do |res|
49
+ response = SsrfFilter.get(uri, headers: headers, request_proc: ->(req) { request = req }, **ssrf_options) do |res|
41
50
  res.body # ensure to read body
42
51
  end
43
52
  end
@@ -59,22 +68,42 @@ module CarrierWave
59
68
  ##
60
69
  # Processes the given URL by parsing it, and escaping if necessary. Public to allow overriding.
61
70
  #
71
+ # Never decodes, as that would make %2F and '/', %2B and '+' indistinguishable.
72
+ #
62
73
  # === Parameters
63
74
  #
64
75
  # [url (String)] The URL where the remote file is stored
65
76
  #
66
77
  def process_uri(source)
67
- uri = Addressable::URI.parse(source)
78
+ uri = Addressable::URI.parse(normalize_input(source))
68
79
  uri.host = uri.normalized_host
69
- # Perform decode first, as the path is likely to be already encoded
70
- uri.path = encode_path(decode_uri(uri.path)) if uri.path =~ CarrierWave::Utilities::Uri::PATH_UNSAFE
71
- uri.query = encode_non_ascii(uri.query) if uri.query
72
- uri.fragment = encode_non_ascii(uri.fragment) if uri.fragment
80
+ uri.path = sanitize_component(uri.path, SANITIZE_PATH) if uri.path
81
+ uri.query = sanitize_component(uri.query, SANITIZE_QUERY) if uri.query
82
+ uri.fragment = sanitize_component(uri.fragment, SANITIZE_FRAGMENT) if uri.fragment
73
83
  URI.parse(uri.to_s)
74
84
  rescue URI::InvalidURIError, Addressable::URI::InvalidURIError
75
85
  raise CarrierWave::DownloadError, "couldn't parse URL: #{source}"
76
86
  end
77
87
 
88
+ ##
89
+ # Cleans up a URL as pasted by an end user, since `remote_#{column}_url=` is meant to be
90
+ # fed from a text field. Public to allow overriding.
91
+ #
92
+ # === Parameters
93
+ #
94
+ # [source (String)] The URL given by the user
95
+ #
96
+ def normalize_input(source)
97
+ source = source.to_s.gsub(LEADING_TRAILING_JUNK, '').gsub(EMBEDDED_JUNK, '')
98
+
99
+ return "#{DEFAULT_SCHEME}:#{source}" if source.start_with?('//')
100
+ # Don't promote a local path like '/etc/passwd' into a URL
101
+ return source if source.start_with?('/')
102
+ return "#{DEFAULT_SCHEME}://#{source}" if source.match?(HOSTISH)
103
+
104
+ source
105
+ end
106
+
78
107
  ##
79
108
  # If this returns true, SSRF protection will be bypassed.
80
109
  # You can override this if you want to allow accessing specific local URIs that are not SSRF exploitable.
@@ -1,6 +1,8 @@
1
1
  module CarrierWave
2
2
  module Downloader
3
3
  class RemoteFile
4
+ include CarrierWave::Utilities::Uri
5
+
4
6
  attr_reader :file, :uri
5
7
 
6
8
  def initialize(file)
@@ -53,7 +55,7 @@ module CarrierWave
53
55
  end
54
56
 
55
57
  def filename_from_uri
56
- CGI.unescape(File.basename(uri.path))
58
+ decode_path(File.basename(uri.path))
57
59
  end
58
60
 
59
61
  def method_missing(*args, &block)
@@ -5,4 +5,5 @@ module CarrierWave
5
5
  class ProcessingError < UploadError; end
6
6
  class DownloadError < UploadError; end
7
7
  class UnknownStorageError < StandardError; end
8
+ class FilenameNotReproducible < StandardError; end
8
9
  end
@@ -9,6 +9,8 @@ en:
9
9
  content_type_allowlist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
10
10
  content_type_denylist_error: "You are not allowed to upload %{content_type} files"
11
11
  processing_error: "Failed to manipulate, maybe it is not an image?"
12
+ unsupported_image_format_error: "Failed to manipulate, file format is not supported"
13
+ filename_too_long_error: "File name is too long"
12
14
  min_size_error: "File size should be greater than %{min_size}"
13
15
  max_size_error: "File size should be less than %{max_size}"
14
16
  min_width_error: "Image width should be greater than %{min_width}px"
@@ -102,6 +102,7 @@ module CarrierWave
102
102
  # === Options
103
103
  #
104
104
  # [:mount_on => Symbol] if the name of the column to be serialized to differs you can override it using this option
105
+ # [:metadata_column => Symbol] the column to record what was stored in, so that it does not have to be worked out again
105
106
  # [:ignore_integrity_errors => Boolean] if set to true, integrity errors will result in caching failing silently
106
107
  # [:ignore_processing_errors => Boolean] if set to true, processing errors will result in caching failing silently
107
108
  #
@@ -174,6 +175,10 @@ module CarrierWave
174
175
  _mounter(:#{column}).read_identifiers[0]
175
176
  end
176
177
 
178
+ def #{column}_identifier=(identifier)
179
+ _mounter(:#{column}).identifiers = [identifier]
180
+ end
181
+
177
182
  def #{column}_integrity_error
178
183
  #{column}_integrity_errors.last
179
184
  end
@@ -247,6 +252,7 @@ module CarrierWave
247
252
  # === Options
248
253
  #
249
254
  # [:mount_on => Symbol] if the name of the column to be serialized to differs you can override it using this option
255
+ # [:metadata_column => Symbol] the column to record what was stored in, so that it does not have to be worked out again
250
256
  # [:ignore_integrity_errors => Boolean] if set to true, integrity errors will result in caching failing silently
251
257
  # [:ignore_processing_errors => Boolean] if set to true, processing errors will result in caching failing silently
252
258
  #
@@ -319,6 +325,10 @@ module CarrierWave
319
325
  def #{column}_identifiers
320
326
  _mounter(:#{column}).read_identifiers
321
327
  end
328
+
329
+ def #{column}_identifiers=(identifiers)
330
+ _mounter(:#{column}).identifiers = identifiers
331
+ end
322
332
  RUBY
323
333
  end
324
334
 
@@ -1,3 +1,6 @@
1
+ require "json"
2
+ require "active_support/core_ext/array/wrap"
3
+
1
4
  module CarrierWave
2
5
 
3
6
  # this is an internal class, used by CarrierWave::Mount so that
@@ -8,6 +11,10 @@ module CarrierWave
8
11
  uploaders.first&.identifier
9
12
  end
10
13
 
14
+ def metadata
15
+ uploaders.first&.build_metadata
16
+ end
17
+
11
18
  def temporary_identifier
12
19
  temporary_identifiers.first
13
20
  end
@@ -18,6 +25,10 @@ module CarrierWave
18
25
  uploaders.map(&:identifier).presence
19
26
  end
20
27
 
28
+ def metadata
29
+ uploaders.map(&:build_metadata).presence
30
+ end
31
+
21
32
  def temporary_identifier
22
33
  temporary_identifiers.presence
23
34
  end
@@ -63,18 +74,35 @@ module CarrierWave
63
74
  [record.read_uploader(serialization_column)].flatten.reject(&:blank?)
64
75
  end
65
76
 
77
+ ##
78
+ # The stored metadata, as many as there are identifiers. Whatever the column gives
79
+ # is accepted, so that it can be a json column, a serialized one, or plain text.
80
+ #
81
+ def read_metadata
82
+ return [] unless metadata_column
83
+
84
+ value = record.read_uploader(metadata_column)
85
+ value = JSON.parse(value) rescue nil if value.is_a?(String)
86
+ Array.wrap(value)
87
+ end
88
+
66
89
  def uploaders
67
- @uploaders ||= read_identifiers.map do |identifier|
68
- uploader = blank_uploader
69
- uploader.retrieve_from_store!(identifier)
70
- uploader
90
+ @uploaders ||= begin
91
+ metadata = read_metadata
92
+ read_identifiers.each_with_index.map do |identifier, index|
93
+ uploader = blank_uploader
94
+ # Has to come first, as retrieval derives the path from what was recorded
95
+ uploader.metadata = metadata[index]
96
+ uploader.retrieve_from_store!(identifier)
97
+ uploader
98
+ end
71
99
  end
72
100
  end
73
101
 
74
102
  def cache(new_files)
75
103
  return if !new_files.is_a?(Array) && new_files.blank?
76
104
  old_uploaders = uploaders
77
- @uploaders = new_files.map do |new_file|
105
+ attempts = new_files.map do |new_file|
78
106
  handle_error do
79
107
  if new_file.is_a?(String)
80
108
  if (uploader = old_uploaders.detect { |old_uploader| old_uploader.identifier == new_file })
@@ -95,12 +123,36 @@ module CarrierWave
95
123
  uploader
96
124
  end
97
125
  end
98
- end.reject(&:blank?)
126
+ end
127
+ errors, new_uploaders = attempts.partition { |attempt| attempt.is_a?(CarrierWave::UploadError) }
128
+ # A refusal didn't ask for the mounted file to go
129
+ return if errors.any? && new_uploaders.all?(&:blank?)
130
+
131
+ @uploaders = new_uploaders.reject(&:blank?)
132
+ @removed_uploaders += (old_uploaders - @uploaders)
133
+ write_temporary_identifier
134
+ end
135
+
136
+ ##
137
+ # Takes the stored files with the given identifiers as the mounted ones, for when
138
+ # they were put in the store by something other than CarrierWave.
139
+ #
140
+ def identifiers=(new_identifiers)
141
+ return if !new_identifiers.is_a?(Array) && new_identifiers.blank?
142
+
143
+ old_uploaders = uploaders
144
+ @uploaders = new_identifiers.map do |identifier|
145
+ next if identifier.blank?
146
+
147
+ blank_uploader.tap { |uploader| uploader.adopt!(identifier) }
148
+ end.compact
99
149
  @removed_uploaders += (old_uploaders - @uploaders)
100
150
  write_temporary_identifier
101
151
  end
102
152
 
103
153
  def cache_names
154
+ # The names are carried over to the next request, which the files have to survive
155
+ uploaders.each(&:materialize_cache!)
104
156
  uploaders.map(&:cache_name).compact
105
157
  end
106
158
 
@@ -128,14 +180,20 @@ module CarrierWave
128
180
  @download_errors.clear
129
181
  @remote_urls = urls
130
182
 
131
- clear_unstaged
132
- @remote_urls.zip(remote_request_headers || []) do |url, header|
183
+ old_uploaders = uploaders
184
+ attempts = @remote_urls.zip(remote_request_headers || []).map do |url, header|
133
185
  handle_error do
134
186
  uploader = blank_uploader
135
187
  uploader.download!(url, header || {})
136
- @uploaders << uploader
188
+ uploader
137
189
  end
138
190
  end
191
+ errors, new_uploaders = attempts.partition { |attempt| attempt.is_a?(CarrierWave::UploadError) }
192
+ # A refusal didn't ask for the mounted file to go
193
+ return if errors.any? && new_uploaders.all?(&:blank?)
194
+
195
+ @uploaders = old_uploaders.select(&:staged) + new_uploaders.reject(&:blank?)
196
+ @removed_uploaders += (old_uploaders - @uploaders)
139
197
  write_temporary_identifier
140
198
  end
141
199
 
@@ -157,6 +215,7 @@ module CarrierWave
157
215
  @added_uploaders += additions
158
216
 
159
217
  record.write_uploader(serialization_column, identifier)
218
+ record.write_uploader(metadata_column, metadata) if metadata_column
160
219
  end
161
220
 
162
221
  def urls(*args)
@@ -178,7 +237,8 @@ module CarrierWave
178
237
  end
179
238
 
180
239
  def remove!
181
- uploaders.each(&:remove!)
240
+ # The reader leaves a blank uploader in place, which has nothing to remove
241
+ uploaders.reject { |uploader| uploader.file.nil? }.each(&:remove!)
182
242
  clear!
183
243
  end
184
244
 
@@ -197,10 +257,14 @@ module CarrierWave
197
257
  option(:mount_on) || column
198
258
  end
199
259
 
260
+ def metadata_column
261
+ option(:metadata_column)
262
+ end
263
+
200
264
  def remove_previous
201
265
  current_paths = uploaders.map(&:path)
202
266
  @removed_uploaders
203
- .reject {|uploader| current_paths.include?(uploader.path) }
267
+ .reject {|uploader| uploader.file.nil? || current_paths.include?(uploader.path) }
204
268
  .each { |uploader| uploader.remove! if uploader.remove_previously_stored_files_after_update }
205
269
  reset_changes!
206
270
  end
@@ -227,17 +291,21 @@ module CarrierWave
227
291
  @removed_uploaders += unstaged
228
292
  end
229
293
 
294
+ # Records the error and gives it back, unless the mount wants it raised
230
295
  def handle_error
231
296
  yield
232
297
  rescue CarrierWave::DownloadError => e
233
298
  @download_errors << e
234
299
  raise e unless option(:ignore_download_errors)
300
+ e
235
301
  rescue CarrierWave::ProcessingError => e
236
302
  @processing_errors << e
237
303
  raise e unless option(:ignore_processing_errors)
304
+ e
238
305
  rescue CarrierWave::IntegrityError => e
239
306
  @integrity_errors << e
240
307
  raise e unless option(:ignore_integrity_errors)
308
+ e
241
309
  end
242
310
 
243
311
  def write_temporary_identifier