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
@@ -0,0 +1,315 @@
1
+ module CarrierWave
2
+
3
+ ##
4
+ # This module simplifies manipulation with vips by providing a set
5
+ # of convenient helper methods. If you want to use them, you'll need to
6
+ # require this file:
7
+ #
8
+ # require 'carrierwave/processing/vips'
9
+ #
10
+ # And then include it in your uploader:
11
+ #
12
+ # class MyUploader < CarrierWave::Uploader::Base
13
+ # include CarrierWave::Vips
14
+ # end
15
+ #
16
+ # You can now use the provided helpers:
17
+ #
18
+ # class MyUploader < CarrierWave::Uploader::Base
19
+ # include CarrierWave::Vips
20
+ #
21
+ # process :resize_to_fit => [200, 200]
22
+ # end
23
+ #
24
+ # Or create your own helpers with the powerful vips! method, which
25
+ # yields an ImageProcessing::Builder object. Check out the ImageProcessing
26
+ # docs at http://github.com/janko-m/image_processing and the list of all
27
+ # available Vips options at
28
+ # https://libvips.github.io/libvips/API/current/using-cli.html for more info.
29
+ #
30
+ # class MyUploader < CarrierWave::Uploader::Base
31
+ # include CarrierWave::Vips
32
+ #
33
+ # process :radial_blur => 10
34
+ #
35
+ # def radial_blur(amount)
36
+ # vips! do |builder|
37
+ # builder.radial_blur(amount)
38
+ # builder = yield(builder) if block_given?
39
+ # builder
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # === Note
45
+ #
46
+ # The ImageProcessing gem uses ruby-vips, a binding for the vips image
47
+ # library. You can find more information here:
48
+ #
49
+ # https://github.com/libvips/ruby-vips
50
+ #
51
+ #
52
+ module Vips
53
+ extend ActiveSupport::Concern
54
+
55
+ included do
56
+ require "image_processing/vips"
57
+ # We need to disable caching since we're editing images in place.
58
+ ::Vips.cache_set_max(0)
59
+ end
60
+
61
+ module ClassMethods
62
+ def convert(format)
63
+ process :convert => format
64
+ end
65
+
66
+ def resize_to_limit(width, height)
67
+ process :resize_to_limit => [width, height]
68
+ end
69
+
70
+ def resize_to_fit(width, height)
71
+ process :resize_to_fit => [width, height]
72
+ end
73
+
74
+ def resize_to_fill(width, height, gravity='centre')
75
+ process :resize_to_fill => [width, height, gravity]
76
+ end
77
+
78
+ def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil)
79
+ process :resize_and_pad => [width, height, background, gravity, alpha]
80
+ end
81
+
82
+ def crop(left, top, width, height)
83
+ process :crop => [left, top, width, height]
84
+ end
85
+ end
86
+
87
+ ##
88
+ # Changes the image encoding format to the given format
89
+ #
90
+ # See https://libvips.github.io/libvips/API/current/using-cli.html#using-command-line-conversion
91
+ #
92
+ # === Parameters
93
+ #
94
+ # [format (#to_s)] an abbreviation of the format
95
+ #
96
+ # === Yields
97
+ #
98
+ # [Vips::Image] additional manipulations to perform
99
+ #
100
+ # === Examples
101
+ #
102
+ # image.convert(:png)
103
+ #
104
+ def convert(format, page=nil)
105
+ vips! do |builder|
106
+ builder = builder.convert(format)
107
+ builder = builder.loader(page: page) if page
108
+ builder
109
+ end
110
+ end
111
+
112
+ ##
113
+ # Resize the image to fit within the specified dimensions while retaining
114
+ # the original aspect ratio. Will only resize the image if it is larger than the
115
+ # specified dimensions. The resulting image may be shorter or narrower than specified
116
+ # in the smaller dimension but will not be larger than the specified values.
117
+ #
118
+ # === Parameters
119
+ #
120
+ # [width (Integer)] the width to scale the image to
121
+ # [height (Integer)] the height to scale the image to
122
+ # [combine_options (Hash)] additional Vips options to apply before resizing
123
+ #
124
+ # === Yields
125
+ #
126
+ # [Vips::Image] additional manipulations to perform
127
+ #
128
+ def resize_to_limit(width, height, combine_options: {})
129
+ width, height = resolve_dimensions(width, height)
130
+
131
+ vips! do |builder|
132
+ builder.resize_to_limit(width, height)
133
+ .apply(combine_options)
134
+ end
135
+ end
136
+
137
+ ##
138
+ # Resize the image to fit within the specified dimensions while retaining
139
+ # the original aspect ratio. The image may be shorter or narrower than
140
+ # specified in the smaller dimension but will not be larger than the specified values.
141
+ #
142
+ # === Parameters
143
+ #
144
+ # [width (Integer)] the width to scale the image to
145
+ # [height (Integer)] the height to scale the image to
146
+ # [combine_options (Hash)] additional Vips options to apply before resizing
147
+ #
148
+ # === Yields
149
+ #
150
+ # [Vips::Image] additional manipulations to perform
151
+ #
152
+ def resize_to_fit(width, height, combine_options: {})
153
+ width, height = resolve_dimensions(width, height)
154
+
155
+ vips! do |builder|
156
+ builder.resize_to_fit(width, height)
157
+ .apply(combine_options)
158
+ end
159
+ end
160
+
161
+ ##
162
+ # Resize the image to fit within the specified dimensions while retaining
163
+ # the aspect ratio of the original image. If necessary, crop the image in the
164
+ # larger dimension.
165
+ #
166
+ # === Parameters
167
+ #
168
+ # [width (Integer)] the width to scale the image to
169
+ # [height (Integer)] the height to scale the image to
170
+ # [combine_options (Hash)] additional vips options to apply before resizing
171
+ #
172
+ # === Yields
173
+ #
174
+ # [Vips::Image] additional manipulations to perform
175
+ #
176
+ def resize_to_fill(width, height, _gravity = nil, combine_options: {})
177
+ width, height = resolve_dimensions(width, height)
178
+
179
+ vips! do |builder|
180
+ builder.resize_to_fill(width, height).apply(combine_options)
181
+ end
182
+ end
183
+
184
+ ##
185
+ # Resize the image to fit within the specified dimensions while retaining
186
+ # the original aspect ratio. If necessary, will pad the remaining area
187
+ # with the given color, which defaults to transparent (for gif and png,
188
+ # white for jpeg).
189
+ #
190
+ # See https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsCompassDirection
191
+ # for gravity options.
192
+ #
193
+ # === Parameters
194
+ #
195
+ # [width (Integer)] the width to scale the image to
196
+ # [height (Integer)] the height to scale the image to
197
+ # [background (List, nil)] the color of the background as a RGB, like [0, 255, 255], nil indicates transparent
198
+ # [gravity (String)] how to position the image
199
+ # [alpha (Boolean, nil)] pad the image with the alpha channel if supported
200
+ # [combine_options (Hash)] additional vips options to apply before resizing
201
+ #
202
+ # === Yields
203
+ #
204
+ # [Vips::Image] additional manipulations to perform
205
+ #
206
+ def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil, combine_options: {})
207
+ width, height = resolve_dimensions(width, height)
208
+
209
+ vips! do |builder|
210
+ builder.resize_and_pad(width, height, background: background, gravity: gravity, alpha: alpha)
211
+ .apply(combine_options)
212
+ end
213
+ end
214
+
215
+ ##
216
+ # Crop the image to the contents of a box positioned at [left] and [top], with the dimensions given
217
+ # by [width] and [height]. The original image bottom/right edge is preserved if the cropping box falls
218
+ # outside the image bounds.
219
+ #
220
+ # === Parameters
221
+ #
222
+ # [left (integer)] left edge of area to extract
223
+ # [top (integer)] top edge of area to extract
224
+ # [width (Integer)] width of area to extract
225
+ # [height (Integer)] height of area to extract
226
+ #
227
+ # === Yields
228
+ #
229
+ # [Vips::Image] additional manipulations to perform
230
+ #
231
+ def crop(left, top, width, height, combine_options: {})
232
+ width, height = resolve_dimensions(width, height)
233
+ width = vips_image.width - left if width + left > vips_image.width
234
+ height = vips_image.height - top if height + top > vips_image.height
235
+
236
+ vips! do |builder|
237
+ builder.crop(left, top, width, height)
238
+ .apply(combine_options)
239
+ end
240
+ end
241
+
242
+ ##
243
+ # Returns the width of the image in pixels.
244
+ #
245
+ # === Returns
246
+ #
247
+ # [Integer] the image's width in pixels
248
+ #
249
+ def width
250
+ vips_image.width
251
+ end
252
+
253
+ ##
254
+ # Returns the height of the image in pixels.
255
+ #
256
+ # === Returns
257
+ #
258
+ # [Integer] the image's height in pixels
259
+ #
260
+ def height
261
+ vips_image.height
262
+ end
263
+
264
+ # Process the image with vip, using the ImageProcessing gem. This
265
+ # method will build a "convert" vips command and execute it on the
266
+ # current image.
267
+ #
268
+ # === Gotcha
269
+ #
270
+ # This method assumes that the object responds to +current_path+.
271
+ # Any class that this module is mixed into must have a +current_path+ method.
272
+ # CarrierWave::Uploader does, so you won't need to worry about this in
273
+ # most cases.
274
+ #
275
+ # === Yields
276
+ #
277
+ # [ImageProcessing::Builder] use it to define processing to be performed
278
+ #
279
+ # === Raises
280
+ #
281
+ # [CarrierWave::ProcessingError] if processing failed.
282
+ def vips!
283
+ builder = ImageProcessing::Vips.source(current_path)
284
+ builder = yield(builder)
285
+
286
+ result = builder.call
287
+ result.close
288
+
289
+ FileUtils.mv result.path, current_path
290
+
291
+ if File.extname(result.path) != File.extname(current_path)
292
+ move_to = current_path.chomp(File.extname(current_path)) + File.extname(result.path)
293
+ file.content_type = Marcel::Magic.by_path(move_to).try(:type)
294
+ file.move_to(move_to, permissions, directory_permissions)
295
+ end
296
+ rescue ::Vips::Error
297
+ message = I18n.translate(:"errors.messages.processing_error")
298
+ raise CarrierWave::ProcessingError, message
299
+ end
300
+
301
+ private
302
+
303
+ def resolve_dimensions(*dimensions)
304
+ dimensions.map do |value|
305
+ next value unless value.instance_of?(Proc)
306
+ value.arity >= 1 ? value.call(self) : value.call
307
+ end
308
+ end
309
+
310
+ def vips_image
311
+ ::Vips::Image.new_from_buffer(read, "")
312
+ end
313
+
314
+ end # Vips
315
+ end # CarrierWave
@@ -1,2 +1,3 @@
1
1
  require "carrierwave/processing/rmagick"
2
2
  require "carrierwave/processing/mini_magick"
3
+ require "carrierwave/processing/vips"
@@ -1,12 +1,5 @@
1
1
  require 'pathname'
2
- require 'active_support/core_ext/string/multibyte'
3
-
4
- begin
5
- # Use mime/types/columnar if available, for reduced memory usage
6
- require 'mime/types/columnar'
7
- rescue LoadError
8
- require 'mime/types'
9
- end
2
+ require 'marcel'
10
3
 
11
4
  module CarrierWave
12
5
 
@@ -19,6 +12,7 @@ module CarrierWave
19
12
  # It's probably needlessly comprehensive and complex. Help is appreciated.
20
13
  #
21
14
  class SanitizedFile
15
+ include CarrierWave::Utilities::FileName
22
16
 
23
17
  attr_reader :file
24
18
 
@@ -32,7 +26,7 @@ module CarrierWave
32
26
 
33
27
  def initialize(file)
34
28
  self.file = file
35
- @content = nil
29
+ @content = @content_type = nil
36
30
  end
37
31
 
38
32
  ##
@@ -44,7 +38,7 @@ module CarrierWave
44
38
  #
45
39
  def original_filename
46
40
  return @original_filename if @original_filename
47
- if @file and @file.respond_to?(:original_filename)
41
+ if @file && @file.respond_to?(:original_filename)
48
42
  @file.original_filename
49
43
  elsif path
50
44
  File.basename(path)
@@ -64,29 +58,6 @@ module CarrierWave
64
58
 
65
59
  alias_method :identifier, :filename
66
60
 
67
- ##
68
- # Returns the part of the filename before the extension. So if a file is called 'test.jpeg'
69
- # this would return 'test'
70
- #
71
- # === Returns
72
- #
73
- # [String] the first part of the filename
74
- #
75
- def basename
76
- split_extension(filename)[0] if filename
77
- end
78
-
79
- ##
80
- # Returns the file extension
81
- #
82
- # === Returns
83
- #
84
- # [String] the extension
85
- #
86
- def extension
87
- split_extension(filename)[1] if filename
88
- end
89
-
90
61
  ##
91
62
  # Returns the file's size.
92
63
  #
@@ -137,7 +108,7 @@ module CarrierWave
137
108
  # [Boolean] whether the file is valid and has a non-zero size
138
109
  #
139
110
  def empty?
140
- @file.nil? || self.size.nil? || (self.size.zero? && ! self.exists?)
111
+ @file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
141
112
  end
142
113
 
143
114
  ##
@@ -156,19 +127,36 @@ module CarrierWave
156
127
  #
157
128
  # [String] contents of the file
158
129
  #
159
- def read
160
- if @content
161
- @content
162
- elsif is_path?
163
- File.open(@file, "rb") {|file| file.read}
130
+ def read(*args)
131
+ if args.empty?
132
+ if @content
133
+ @content
134
+ elsif is_path?
135
+ File.open(@file, "rb") {|file| file.read }
136
+ else
137
+ @file.try(:rewind)
138
+ @content = @file.read
139
+ @file.try(:close) unless @file.class.ancestors.include?(::StringIO) || @file.try(:closed?)
140
+ @content
141
+ end
164
142
  else
165
- @file.try(:rewind)
166
- @content = @file.read
167
- @file.try(:close) unless @file.try(:closed?)
168
- @content
143
+ if is_path?
144
+ @file = File.open(path, "rb")
145
+ elsif @file.is_a?(CarrierWave::Uploader::Base)
146
+ @file = StringIO.new(@file.read)
147
+ end
148
+
149
+ @file.read(*args)
169
150
  end
170
151
  end
171
152
 
153
+ ##
154
+ # Rewinds the underlying file.
155
+ #
156
+ def rewind
157
+ @file.rewind if @file.respond_to?(:rewind)
158
+ end
159
+
172
160
  ##
173
161
  # Moves the file to the given path
174
162
  #
@@ -185,13 +173,10 @@ module CarrierWave
185
173
  mkdir!(new_path, directory_permissions)
186
174
  move!(new_path)
187
175
  chmod!(new_path, permissions)
188
- if keep_filename
189
- self.file = {:tempfile => new_path, :filename => original_filename, :content_type => content_type}
190
- else
191
- self.file = {:tempfile => new_path, :content_type => content_type}
192
- end
176
+ self.file = {tempfile: new_path, filename: keep_filename ? original_filename : nil, content_type: declared_content_type}
193
177
  self
194
178
  end
179
+
195
180
  ##
196
181
  # Helper to move file to new path.
197
182
  #
@@ -223,7 +208,7 @@ module CarrierWave
223
208
  mkdir!(new_path, directory_permissions)
224
209
  copy!(new_path)
225
210
  chmod!(new_path, permissions)
226
- self.class.new({:tempfile => new_path, :content_type => content_type})
211
+ self.class.new({tempfile: new_path, content_type: declared_content_type})
227
212
  end
228
213
 
229
214
  ##
@@ -264,12 +249,11 @@ module CarrierWave
264
249
  # [String] the content type of the file
265
250
  #
266
251
  def content_type
267
- return @content_type if @content_type
268
- if @file.respond_to?(:content_type) and @file.content_type
269
- @content_type = @file.content_type.to_s.chomp
270
- elsif path
271
- @content_type = ::MIME::Types.type_for(path).first.to_s
272
- end
252
+ @content_type ||=
253
+ identified_content_type ||
254
+ declared_content_type ||
255
+ guessed_safe_content_type ||
256
+ Marcel::MimeType::BINARY
273
257
  end
274
258
 
275
259
  ##
@@ -300,11 +284,11 @@ module CarrierWave
300
284
  if file.is_a?(Hash)
301
285
  @file = file["tempfile"] || file[:tempfile]
302
286
  @original_filename = file["filename"] || file[:filename]
303
- @content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
287
+ @declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
304
288
  else
305
289
  @file = file
306
290
  @original_filename = nil
307
- @content_type = nil
291
+ @declared_content_type = nil
308
292
  end
309
293
  end
310
294
 
@@ -321,28 +305,65 @@ module CarrierWave
321
305
 
322
306
  # Sanitize the filename, to prevent hacking
323
307
  def sanitize(name)
308
+ name = name.scrub
324
309
  name = name.tr("\\", "/") # work-around for IE
325
310
  name = File.basename(name)
326
- name = name.gsub(sanitize_regexp,"_")
311
+ name = name.gsub(sanitize_regexp, "_")
327
312
  name = "_#{name}" if name =~ /\A\.+\z/
328
- name = "unnamed" if name.size == 0
329
- return name.mb_chars.to_s
313
+ name = "unnamed" if name.size.zero?
314
+ name.to_s
315
+ end
316
+
317
+ def declared_content_type
318
+ @declared_content_type ||
319
+ if @file.respond_to?(:content_type) && @file.content_type
320
+ Marcel::MimeType.for(declared_type: first_declared_content_type(@file.content_type))
321
+ end
330
322
  end
331
323
 
332
- def split_extension(filename)
333
- # regular expressions to try for identifying extensions
334
- extension_matchers = [
335
- /\A(.+)\.(tar\.([glx]?z|bz2))\z/, # matches "something.tar.gz"
336
- /\A(.+)\.([^\.]+)\z/ # matches "something.jpg"
337
- ]
324
+ # Clients occasionally send more than one MIME type in a single header
325
+ # (e.g. "image/png; text/html" or "image/png, text/html"). Marcel 2 validates
326
+ # the declared type against the RFC grammar and rejects such values, so pick
327
+ # the first media type ourselves before handing it over.
328
+ def first_declared_content_type(content_type)
329
+ content_type.to_s.strip.split(/[;,\s]/, 2).first
330
+ end
331
+
332
+ # Guess content type from its file extension. Limit what to be returned to prevent spoofing.
333
+ def guessed_safe_content_type
334
+ return unless path
335
+
336
+ type = Marcel::Magic.by_path(original_filename).to_s
337
+ type if type.start_with?('text/') || type.start_with?('application/json')
338
+ end
339
+
340
+ def identified_content_type
341
+ with_io do |io|
342
+ mimetype_by_magic = Marcel::Magic.by_magic(io)
343
+ mimetype_by_path = Marcel::Magic.by_path(path)
338
344
 
339
- extension_matchers.each do |regexp|
340
- if filename =~ regexp
341
- return $1, $2
345
+ return nil if mimetype_by_magic.nil?
346
+
347
+ if mimetype_by_path&.child_of?(mimetype_by_magic.type)
348
+ mimetype_by_path.type
349
+ else
350
+ mimetype_by_magic.type
342
351
  end
343
352
  end
344
- return filename, "" # In case we weren't able to split the extension
353
+ rescue Errno::ENOENT
354
+ nil
345
355
  end
346
356
 
357
+ def with_io(&block)
358
+ if file.is_a?(IO)
359
+ begin
360
+ yield file
361
+ ensure
362
+ file.try(:rewind)
363
+ end
364
+ elsif path
365
+ File.open(path, &block)
366
+ end
367
+ end
347
368
  end # SanitizedFile
348
369
  end # CarrierWave
@@ -14,7 +14,7 @@ module CarrierWave
14
14
  end
15
15
 
16
16
  def identifier
17
- uploader.filename
17
+ uploader.deduplicated_filename
18
18
  end
19
19
 
20
20
  def store!(file)
@@ -24,19 +24,19 @@ module CarrierWave
24
24
  end
25
25
 
26
26
  def cache!(new_file)
27
- raise NotImplementedError.new("Need to implement #cache! if you want to use #{self.class.name} as a cache storage.")
27
+ raise NotImplementedError, "Need to implement #cache! if you want to use #{self.class.name} as a cache storage."
28
28
  end
29
29
 
30
30
  def retrieve_from_cache!(identifier)
31
- raise NotImplementedError.new("Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage.")
31
+ raise NotImplementedError, "Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage."
32
32
  end
33
33
 
34
34
  def delete_dir!(path)
35
- raise NotImplementedError.new("Need to implement #delete_dir! if you want to use #{self.class.name} as a cache storage.")
35
+ raise NotImplementedError, "Need to implement #delete_dir! if you want to use #{self.class.name} as a cache storage."
36
36
  end
37
37
 
38
38
  def clean_cache!(seconds)
39
- raise NotImplementedError.new("Need to implement #clean_cache! if you want to use #{self.class.name} as a cache storage.")
39
+ raise NotImplementedError, "Need to implement #clean_cache! if you want to use #{self.class.name} as a cache storage."
40
40
  end
41
41
  end # Abstract
42
42
  end # Storage
@@ -17,7 +17,7 @@ module CarrierWave
17
17
  #
18
18
  # By default, store!() uses copy_to(), which operates by copying the file
19
19
  # from the cache to the store, then deleting the file from the cache.
20
- # If move_to_store() is overriden to return true, then store!() uses move_to(),
20
+ # If move_to_store() is overridden to return true, then store!() uses move_to(),
21
21
  # which simply moves the file from cache to store. Useful for large files.
22
22
  #
23
23
  # === Parameters
@@ -109,10 +109,11 @@ module CarrierWave
109
109
  end
110
110
 
111
111
  def clean_cache!(seconds)
112
- Dir.glob(::File.expand_path(::File.join(uploader.cache_dir, '*'), CarrierWave.root)).each do |dir|
113
- # generate_cache_id returns key formated TIMEINT-PID-COUNTER-RND
114
- time = dir.scan(/(\d+)-\d+-\d+-\d+/).first.map(&:to_i)
115
- time = Time.at(*time)
112
+ Dir.glob(::File.expand_path(::File.join(uploader.cache_dir, '*'), uploader.root)).each do |dir|
113
+ # generate_cache_id returns key formatted TIMEINT-PID(-COUNTER)-RND
114
+ matched = dir.scan(/(\d+)-\d+-\d+(?:-\d+)?/).first
115
+ next unless matched
116
+ time = Time.at(matched[0].to_i)
116
117
  if time < (Time.now.utc - seconds)
117
118
  FileUtils.rm_rf(dir)
118
119
  end