carrierwave 0.9.0 → 2.1.1

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.

Potentially problematic release.


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

Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +362 -116
  3. data/lib/carrierwave/compatibility/paperclip.rb +29 -21
  4. data/lib/carrierwave/downloader/base.rb +83 -0
  5. data/lib/carrierwave/downloader/remote_file.rb +65 -0
  6. data/lib/carrierwave/error.rb +1 -0
  7. data/lib/carrierwave/locale/en.yml +7 -4
  8. data/lib/carrierwave/mount.rb +238 -186
  9. data/lib/carrierwave/mounter.rb +188 -0
  10. data/lib/carrierwave/orm/activerecord.rb +60 -24
  11. data/lib/carrierwave/processing/mini_magick.rb +139 -78
  12. data/lib/carrierwave/processing/rmagick.rb +68 -23
  13. data/lib/carrierwave/processing.rb +0 -1
  14. data/lib/carrierwave/sanitized_file.rb +67 -27
  15. data/lib/carrierwave/storage/abstract.rb +15 -2
  16. data/lib/carrierwave/storage/file.rb +69 -2
  17. data/lib/carrierwave/storage/fog.rb +180 -41
  18. data/lib/carrierwave/storage.rb +1 -7
  19. data/lib/carrierwave/test/matchers.rb +77 -12
  20. data/lib/carrierwave/uploader/cache.rb +74 -38
  21. data/lib/carrierwave/uploader/callbacks.rb +0 -2
  22. data/lib/carrierwave/uploader/configuration.rb +72 -6
  23. data/lib/carrierwave/uploader/content_type_blacklist.rb +48 -0
  24. data/lib/carrierwave/uploader/content_type_whitelist.rb +48 -0
  25. data/lib/carrierwave/uploader/default_url.rb +3 -5
  26. data/lib/carrierwave/uploader/download.rb +5 -69
  27. data/lib/carrierwave/uploader/extension_blacklist.rb +14 -10
  28. data/lib/carrierwave/uploader/extension_whitelist.rb +13 -10
  29. data/lib/carrierwave/uploader/file_size.rb +43 -0
  30. data/lib/carrierwave/uploader/mountable.rb +13 -8
  31. data/lib/carrierwave/uploader/processing.rb +15 -17
  32. data/lib/carrierwave/uploader/proxy.rb +16 -7
  33. data/lib/carrierwave/uploader/remove.rb +0 -2
  34. data/lib/carrierwave/uploader/serialization.rb +3 -5
  35. data/lib/carrierwave/uploader/store.rb +17 -24
  36. data/lib/carrierwave/uploader/url.rb +3 -5
  37. data/lib/carrierwave/uploader/versions.rb +117 -86
  38. data/lib/carrierwave/uploader.rb +6 -2
  39. data/lib/carrierwave/utilities/uri.rb +5 -6
  40. data/lib/carrierwave/utilities.rb +1 -3
  41. data/lib/carrierwave/validations/active_model.rb +3 -7
  42. data/lib/carrierwave/version.rb +1 -1
  43. data/lib/carrierwave.rb +36 -3
  44. data/lib/generators/templates/uploader.rb +4 -8
  45. data/lib/generators/uploader_generator.rb +1 -1
  46. metadata +195 -94
  47. data/lib/carrierwave/locale/cs.yml +0 -11
  48. data/lib/carrierwave/locale/de.yml +0 -11
  49. data/lib/carrierwave/locale/nl.yml +0 -11
  50. data/lib/carrierwave/locale/sk.yml +0 -11
  51. data/lib/carrierwave/processing/mime_types.rb +0 -73
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
 
5
3
  ##
@@ -69,6 +67,13 @@ module CarrierWave
69
67
  e.message << " (You may need to install the rmagick gem)"
70
68
  raise e
71
69
  end
70
+
71
+ prepend Module.new {
72
+ def initialize(*)
73
+ super
74
+ @format = nil
75
+ end
76
+ }
72
77
  end
73
78
 
74
79
  module ClassMethods
@@ -135,6 +140,8 @@ module CarrierWave
135
140
  # [Magick::Image] additional manipulations to perform
136
141
  #
137
142
  def resize_to_limit(width, height)
143
+ width = dimension_from width
144
+ height = dimension_from height
138
145
  manipulate! do |img|
139
146
  geometry = Magick::Geometry.new(width, height, 0, 0, Magick::GreaterGeometry)
140
147
  new_img = img.change_geometry(geometry) do |new_width, new_height|
@@ -164,6 +171,8 @@ module CarrierWave
164
171
  # [Magick::Image] additional manipulations to perform
165
172
  #
166
173
  def resize_to_fit(width, height)
174
+ width = dimension_from width
175
+ height = dimension_from height
167
176
  manipulate! do |img|
168
177
  img.resize_to_fit!(width, height)
169
178
  img = yield(img) if block_given?
@@ -188,6 +197,8 @@ module CarrierWave
188
197
  # [Magick::Image] additional manipulations to perform
189
198
  #
190
199
  def resize_to_fill(width, height, gravity=::Magick::CenterGravity)
200
+ width = dimension_from width
201
+ height = dimension_from height
191
202
  manipulate! do |img|
192
203
  img.crop_resized!(width, height, gravity)
193
204
  img = yield(img) if block_given?
@@ -213,9 +224,11 @@ module CarrierWave
213
224
  # [Magick::Image] additional manipulations to perform
214
225
  #
215
226
  def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
227
+ width = dimension_from width
228
+ height = dimension_from height
216
229
  manipulate! do |img|
217
230
  img.resize_to_fit!(width, height)
218
- new_img = ::Magick::Image.new(width, height) { self.background_color = 'rgba(255,255,255,0)' }
231
+ new_img = ::Magick::Image.new(width, height) { self.background_color = background == :transparent ? 'rgba(255,255,255,0)' : background.to_s }
219
232
  if background == :transparent
220
233
  filled = new_img.matte_floodfill(1, 1)
221
234
  else
@@ -251,6 +264,28 @@ module CarrierWave
251
264
  end
252
265
  end
253
266
 
267
+ ##
268
+ # Returns the width of the image.
269
+ #
270
+ # === Returns
271
+ #
272
+ # [Integer] the image's width in pixels
273
+ #
274
+ def width
275
+ rmagick_image.columns
276
+ end
277
+
278
+ ##
279
+ # Returns the height of the image.
280
+ #
281
+ # === Returns
282
+ #
283
+ # [Integer] the image's height in pixels
284
+ #
285
+ def height
286
+ rmagick_image.rows
287
+ end
288
+
254
289
  ##
255
290
  # Manipulate the image with RMagick. This method will load up an image
256
291
  # and then pass each of its frames to the supplied block. It will then
@@ -315,46 +350,56 @@ module CarrierWave
315
350
 
316
351
  read_block = create_info_block(options[:read])
317
352
  image = ::Magick::Image.read(current_path, &read_block)
353
+ frames = ::Magick::ImageList.new
318
354
 
319
- frames = if image.size > 1
320
- list = ::Magick::ImageList.new
321
- image.each_with_index do |frame, index|
322
- processed_frame = if block_given?
323
- yield *[frame, index, options].take(block.arity)
324
- else
325
- frame
326
- end
327
- list << processed_frame if processed_frame
328
- end
329
- block_given? ? list : list.append(true)
330
- else
331
- frame = image.first
332
- frame = yield( *[frame, 0, options].take(block.arity) ) if block_given?
333
- frame
355
+ image.each_with_index do |frame, index|
356
+ frame = yield(*[frame, index, options].take(block.arity)) if block_given?
357
+ frames << frame if frame
334
358
  end
359
+ frames.append(true) if block_given?
335
360
 
336
361
  write_block = create_info_block(options[:write])
362
+
337
363
  if options[:format] || @format
338
364
  frames.write("#{options[:format] || @format}:#{current_path}", &write_block)
365
+ move_to = current_path.chomp(File.extname(current_path)) + ".#{options[:format] || @format}"
366
+ file.content_type = ::MiniMime.lookup_by_filename(move_to).content_type
367
+ file.move_to(move_to, permissions, directory_permissions)
339
368
  else
340
369
  frames.write(current_path, &write_block)
341
370
  end
371
+
342
372
  destroy_image(frames)
343
373
  rescue ::Magick::ImageMagickError => e
344
- raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.rmagick_processing_error", :e => e, :default => I18n.translate(:"errors.messages.rmagick_processing_error", :e => e, :locale => :en))
374
+ raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.rmagick_processing_error", :e => e)
345
375
  end
346
376
 
347
377
  private
348
378
 
349
379
  def create_info_block(options)
350
380
  return nil unless options
351
- assignments = options.map { |k, v| "self.#{k} = #{v}" }
352
- code = "lambda { |img| " + assignments.join(";") + "}"
353
- eval code
381
+ proc do |img|
382
+ options.each do |k, v|
383
+ if v.is_a?(String) && (matches = v.match(/^["'](.+)["']/))
384
+ ActiveSupport::Deprecation.warn "Passing quoted strings like #{v} to #manipulate! is deprecated, pass them without quoting."
385
+ v = matches[1]
386
+ end
387
+ img.public_send(:"#{k}=", v)
388
+ end
389
+ end
354
390
  end
355
391
 
356
392
  def destroy_image(image)
357
- image.destroy! if image.respond_to?(:destroy!)
393
+ image.try(:destroy!)
394
+ end
395
+
396
+ def dimension_from(value)
397
+ return value unless value.instance_of?(Proc)
398
+ value.arity >= 1 ? value.call(self) : value.call
399
+ end
400
+
401
+ def rmagick_image
402
+ ::Magick::Image.from_blob(self.read).first
358
403
  end
359
404
 
360
405
  end # RMagick
@@ -1,3 +1,2 @@
1
1
  require "carrierwave/processing/rmagick"
2
2
  require "carrierwave/processing/mini_magick"
3
- require "carrierwave/processing/mime_types"
@@ -1,7 +1,7 @@
1
- # encoding: utf-8
2
-
3
1
  require 'pathname'
4
2
  require 'active_support/core_ext/string/multibyte'
3
+ require 'mini_mime'
4
+ require 'mimemagic'
5
5
 
6
6
  module CarrierWave
7
7
 
@@ -15,18 +15,19 @@ module CarrierWave
15
15
  #
16
16
  class SanitizedFile
17
17
 
18
- attr_accessor :file
18
+ attr_reader :file
19
19
 
20
20
  class << self
21
21
  attr_writer :sanitize_regexp
22
22
 
23
23
  def sanitize_regexp
24
- @sanitize_regexp ||= /[^a-zA-Z0-9\.\-\+_]/
24
+ @sanitize_regexp ||= /[^[:word:]\.\-\+]/
25
25
  end
26
26
  end
27
27
 
28
28
  def initialize(file)
29
29
  self.file = file
30
+ @content = nil
30
31
  end
31
32
 
32
33
  ##
@@ -108,12 +109,11 @@ module CarrierWave
108
109
  # [String, nil] the path where the file is located.
109
110
  #
110
111
  def path
111
- unless @file.blank?
112
- if is_path?
113
- File.expand_path(@file)
114
- elsif @file.respond_to?(:path) and not @file.path.blank?
115
- File.expand_path(@file.path)
116
- end
112
+ return if @file.blank?
113
+ if is_path?
114
+ File.expand_path(@file)
115
+ elsif @file.respond_to?(:path) && !@file.path.blank?
116
+ File.expand_path(@file.path)
117
117
  end
118
118
  end
119
119
 
@@ -141,8 +141,7 @@ module CarrierWave
141
141
  # [Boolean] Whether the file exists
142
142
  #
143
143
  def exists?
144
- return File.exists?(self.path) if self.path
145
- return false
144
+ self.path.present? && File.exist?(self.path)
146
145
  end
147
146
 
148
147
  ##
@@ -158,9 +157,9 @@ module CarrierWave
158
157
  elsif is_path?
159
158
  File.open(@file, "rb") {|file| file.read}
160
159
  else
161
- @file.rewind if @file.respond_to?(:rewind)
160
+ @file.try(:rewind)
162
161
  @content = @file.read
163
- @file.close if @file.respond_to?(:close) && @file.respond_to?(:closed?) && !@file.closed?
162
+ @file.try(:close) unless @file.try(:closed?)
164
163
  @content
165
164
  end
166
165
  end
@@ -174,19 +173,29 @@ module CarrierWave
174
173
  # [permissions (Integer)] permissions to set on the file in its new location.
175
174
  # [directory_permissions (Integer)] permissions to set on created directories.
176
175
  #
177
- def move_to(new_path, permissions=nil, directory_permissions=nil)
176
+ def move_to(new_path, permissions=nil, directory_permissions=nil, keep_filename=false)
178
177
  return if self.empty?
179
178
  new_path = File.expand_path(new_path)
180
179
 
181
180
  mkdir!(new_path, directory_permissions)
181
+ move!(new_path)
182
+ chmod!(new_path, permissions)
183
+ if keep_filename
184
+ self.file = {:tempfile => new_path, :filename => original_filename, :content_type => content_type}
185
+ else
186
+ self.file = {:tempfile => new_path, :content_type => content_type}
187
+ end
188
+ self
189
+ end
190
+ ##
191
+ # Helper to move file to new path.
192
+ #
193
+ def move!(new_path)
182
194
  if exists?
183
- FileUtils.mv(path, new_path) unless new_path == path
195
+ FileUtils.mv(path, new_path) unless File.identical?(new_path, path)
184
196
  else
185
197
  File.open(new_path, "wb") { |f| f.write(read) }
186
198
  end
187
- chmod!(new_path, permissions)
188
- self.file = new_path
189
- self
190
199
  end
191
200
 
192
201
  ##
@@ -207,13 +216,20 @@ module CarrierWave
207
216
  new_path = File.expand_path(new_path)
208
217
 
209
218
  mkdir!(new_path, directory_permissions)
219
+ copy!(new_path)
220
+ chmod!(new_path, permissions)
221
+ self.class.new({:tempfile => new_path, :content_type => content_type})
222
+ end
223
+
224
+ ##
225
+ # Helper to create copy of file in new path.
226
+ #
227
+ def copy!(new_path)
210
228
  if exists?
211
229
  FileUtils.cp(path, new_path) unless new_path == path
212
230
  else
213
231
  File.open(new_path, "wb") { |f| f.write(read) }
214
232
  end
215
- chmod!(new_path, permissions)
216
- self.class.new({:tempfile => new_path, :content_type => content_type})
217
233
  end
218
234
 
219
235
  ##
@@ -243,8 +259,10 @@ module CarrierWave
243
259
  # [String] the content type of the file
244
260
  #
245
261
  def content_type
246
- return @content_type if @content_type
247
- @file.content_type.to_s.chomp if @file.respond_to?(:content_type) and @file.content_type
262
+ @content_type ||=
263
+ existing_content_type ||
264
+ mime_magic_content_type ||
265
+ mini_mime_content_type
248
266
  end
249
267
 
250
268
  ##
@@ -275,7 +293,7 @@ module CarrierWave
275
293
  if file.is_a?(Hash)
276
294
  @file = file["tempfile"] || file[:tempfile]
277
295
  @original_filename = file["filename"] || file[:filename]
278
- @content_type = file["content_type"] || file[:content_type]
296
+ @content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
279
297
  else
280
298
  @file = file
281
299
  @original_filename = nil
@@ -287,7 +305,7 @@ module CarrierWave
287
305
  def mkdir!(path, directory_permissions)
288
306
  options = {}
289
307
  options[:mode] = directory_permissions if directory_permissions
290
- FileUtils.mkdir_p(File.dirname(path), options) unless File.exists?(File.dirname(path))
308
+ FileUtils.mkdir_p(File.dirname(path), **options) unless File.exist?(File.dirname(path))
291
309
  end
292
310
 
293
311
  def chmod!(path, permissions)
@@ -296,14 +314,36 @@ module CarrierWave
296
314
 
297
315
  # Sanitize the filename, to prevent hacking
298
316
  def sanitize(name)
299
- name = name.gsub("\\", "/") # work-around for IE
317
+ name = name.tr("\\", "/") # work-around for IE
300
318
  name = File.basename(name)
301
319
  name = name.gsub(sanitize_regexp,"_")
302
320
  name = "_#{name}" if name =~ /\A\.+\z/
303
- name = "unnamed" if name.size == 0
321
+ name = "unnamed" if name.size.zero?
304
322
  return name.mb_chars.to_s
305
323
  end
306
324
 
325
+ def existing_content_type
326
+ if @file.respond_to?(:content_type) && @file.content_type
327
+ @file.content_type.to_s.chomp
328
+ end
329
+ end
330
+
331
+ def mime_magic_content_type
332
+ if path
333
+ File.open(path) do |file|
334
+ MimeMagic.by_magic(file).try(:type) || 'invalid/invalid'
335
+ end
336
+ end
337
+ rescue Errno::ENOENT
338
+ nil
339
+ end
340
+
341
+ def mini_mime_content_type
342
+ return unless path
343
+ mime_type = ::MiniMime.lookup_by_filename(path)
344
+ @content_type = (mime_type && mime_type.content_type).to_s
345
+ end
346
+
307
347
  def split_extension(filename)
308
348
  # regular expressions to try for identifying extensions
309
349
  extension_matchers = [
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Storage
5
3
 
@@ -25,6 +23,21 @@ module CarrierWave
25
23
  def retrieve!(identifier)
26
24
  end
27
25
 
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.")
28
+ end
29
+
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.")
32
+ end
33
+
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.")
36
+ end
37
+
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.")
40
+ end
28
41
  end # Abstract
29
42
  end # Storage
30
43
  end # CarrierWave
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Storage
5
3
 
@@ -9,6 +7,10 @@ module CarrierWave
9
7
  # pretty much it.
10
8
  #
11
9
  class File < Abstract
10
+ def initialize(*)
11
+ super
12
+ @cache_called = nil
13
+ end
12
14
 
13
15
  ##
14
16
  # Move the file to the uploader's store path.
@@ -51,6 +53,71 @@ module CarrierWave
51
53
  CarrierWave::SanitizedFile.new(path)
52
54
  end
53
55
 
56
+ ##
57
+ # Stores given file to cache directory.
58
+ #
59
+ # === Parameters
60
+ #
61
+ # [new_file (File, IOString, Tempfile)] any kind of file object
62
+ #
63
+ # === Returns
64
+ #
65
+ # [CarrierWave::SanitizedFile] a sanitized file
66
+ #
67
+ def cache!(new_file)
68
+ new_file.move_to(::File.expand_path(uploader.cache_path, uploader.root), uploader.permissions, uploader.directory_permissions, true)
69
+ rescue Errno::EMLINK, Errno::ENOSPC => e
70
+ raise(e) if @cache_called
71
+ @cache_called = true
72
+
73
+ # NOTE: Remove cached files older than 10 minutes
74
+ clean_cache!(600)
75
+
76
+ cache!(new_file)
77
+ end
78
+
79
+ ##
80
+ # Retrieves the file with the given cache_name from the cache.
81
+ #
82
+ # === Parameters
83
+ #
84
+ # [cache_name (String)] uniquely identifies a cache file
85
+ #
86
+ # === Raises
87
+ #
88
+ # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
89
+ #
90
+ def retrieve_from_cache!(identifier)
91
+ CarrierWave::SanitizedFile.new(::File.expand_path(uploader.cache_path(identifier), uploader.root))
92
+ end
93
+
94
+ ##
95
+ # Deletes a cache dir
96
+ #
97
+ def delete_dir!(path)
98
+ if path
99
+ begin
100
+ Dir.rmdir(::File.expand_path(path, uploader.root))
101
+ rescue Errno::ENOENT
102
+ # Ignore: path does not exist
103
+ rescue Errno::ENOTDIR
104
+ # Ignore: path is not a dir
105
+ rescue Errno::ENOTEMPTY, Errno::EEXIST
106
+ # Ignore: dir is not empty
107
+ end
108
+ end
109
+ end
110
+
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)
116
+ if time < (Time.now.utc - seconds)
117
+ FileUtils.rm_rf(dir)
118
+ end
119
+ end
120
+ end
54
121
  end # File
55
122
  end # Storage
56
123
  end # CarrierWave