carrierwave 0.11.2 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +220 -116
  3. data/lib/carrierwave.rb +8 -10
  4. data/lib/carrierwave/compatibility/paperclip.rb +0 -2
  5. data/lib/carrierwave/error.rb +1 -0
  6. data/lib/carrierwave/locale/en.yml +7 -4
  7. data/lib/carrierwave/mount.rb +209 -176
  8. data/lib/carrierwave/mounter.rb +163 -0
  9. data/lib/carrierwave/orm/activerecord.rb +49 -20
  10. data/lib/carrierwave/processing.rb +0 -1
  11. data/lib/carrierwave/processing/mini_magick.rb +64 -13
  12. data/lib/carrierwave/processing/rmagick.rb +31 -3
  13. data/lib/carrierwave/sanitized_file.rb +43 -38
  14. data/lib/carrierwave/storage.rb +0 -9
  15. data/lib/carrierwave/storage/abstract.rb +15 -2
  16. data/lib/carrierwave/storage/file.rb +64 -2
  17. data/lib/carrierwave/storage/fog.rb +100 -27
  18. data/lib/carrierwave/test/matchers.rb +77 -12
  19. data/lib/carrierwave/uploader.rb +2 -2
  20. data/lib/carrierwave/uploader/cache.rb +40 -26
  21. data/lib/carrierwave/uploader/callbacks.rb +0 -2
  22. data/lib/carrierwave/uploader/configuration.rb +48 -6
  23. data/lib/carrierwave/uploader/default_url.rb +3 -5
  24. data/lib/carrierwave/uploader/download.rb +2 -4
  25. data/lib/carrierwave/uploader/extension_blacklist.rb +14 -10
  26. data/lib/carrierwave/uploader/extension_whitelist.rb +12 -10
  27. data/lib/carrierwave/uploader/file_size.rb +41 -0
  28. data/lib/carrierwave/uploader/magic_mime_blacklist.rb +94 -0
  29. data/lib/carrierwave/uploader/magic_mime_whitelist.rb +94 -0
  30. data/lib/carrierwave/uploader/mountable.rb +7 -8
  31. data/lib/carrierwave/uploader/processing.rb +1 -3
  32. data/lib/carrierwave/uploader/proxy.rb +5 -7
  33. data/lib/carrierwave/uploader/remove.rb +0 -2
  34. data/lib/carrierwave/uploader/serialization.rb +1 -3
  35. data/lib/carrierwave/uploader/store.rb +5 -23
  36. data/lib/carrierwave/uploader/url.rb +1 -3
  37. data/lib/carrierwave/uploader/versions.rb +75 -82
  38. data/lib/carrierwave/utilities.rb +0 -3
  39. data/lib/carrierwave/utilities/uri.rb +4 -7
  40. data/lib/carrierwave/validations/active_model.rb +0 -2
  41. data/lib/carrierwave/version.rb +1 -1
  42. data/lib/generators/templates/uploader.rb +3 -5
  43. metadata +43 -97
  44. data/lib/carrierwave/locale/cs.yml +0 -11
  45. data/lib/carrierwave/locale/de.yml +0 -11
  46. data/lib/carrierwave/locale/el.yml +0 -11
  47. data/lib/carrierwave/locale/es.yml +0 -11
  48. data/lib/carrierwave/locale/fr.yml +0 -11
  49. data/lib/carrierwave/locale/ja.yml +0 -11
  50. data/lib/carrierwave/locale/nb.yml +0 -11
  51. data/lib/carrierwave/locale/nl.yml +0 -11
  52. data/lib/carrierwave/locale/pl.yml +0 -11
  53. data/lib/carrierwave/locale/pt-BR.yml +0 -11
  54. data/lib/carrierwave/locale/pt-PT.yml +0 -11
  55. data/lib/carrierwave/locale/ru.yml +0 -11
  56. data/lib/carrierwave/locale/sk.yml +0 -11
  57. data/lib/carrierwave/locale/tr.yml +0 -11
  58. data/lib/carrierwave/processing/mime_types.rb +0 -74
  59. data/lib/carrierwave/utilities/deprecation.rb +0 -18
@@ -1,9 +1,12 @@
1
- # encoding: utf-8
2
-
3
1
  require 'pathname'
4
2
  require 'active_support/core_ext/string/multibyte'
5
- require 'mime/types'
6
- require 'mimemagic'
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
7
10
 
8
11
  module CarrierWave
9
12
 
@@ -23,7 +26,7 @@ module CarrierWave
23
26
  attr_writer :sanitize_regexp
24
27
 
25
28
  def sanitize_regexp
26
- @sanitize_regexp ||= /[^a-zA-Z0-9\.\-\+_]/
29
+ @sanitize_regexp ||= /[^[:word:]\.\-\+]/
27
30
  end
28
31
  end
29
32
 
@@ -143,8 +146,7 @@ module CarrierWave
143
146
  # [Boolean] Whether the file exists
144
147
  #
145
148
  def exists?
146
- return File.exists?(self.path) if self.path
147
- return false
149
+ self.path.present? && File.exist?(self.path)
148
150
  end
149
151
 
150
152
  ##
@@ -160,9 +162,9 @@ module CarrierWave
160
162
  elsif is_path?
161
163
  File.open(@file, "rb") {|file| file.read}
162
164
  else
163
- @file.rewind if @file.respond_to?(:rewind)
165
+ @file.try(:rewind)
164
166
  @content = @file.read
165
- @file.close if @file.respond_to?(:close) && @file.respond_to?(:closed?) && !@file.closed?
167
+ @file.try(:close) unless @file.try(:closed?)
166
168
  @content
167
169
  end
168
170
  end
@@ -176,19 +178,29 @@ module CarrierWave
176
178
  # [permissions (Integer)] permissions to set on the file in its new location.
177
179
  # [directory_permissions (Integer)] permissions to set on created directories.
178
180
  #
179
- def move_to(new_path, permissions=nil, directory_permissions=nil)
181
+ def move_to(new_path, permissions=nil, directory_permissions=nil, keep_filename=false)
180
182
  return if self.empty?
181
183
  new_path = File.expand_path(new_path)
182
184
 
183
185
  mkdir!(new_path, directory_permissions)
186
+ move!(new_path)
187
+ chmod!(new_path, permissions)
188
+ if keep_filename
189
+ self.file = {:tempfile => new_path, :filename => original_filename}
190
+ else
191
+ self.file = new_path
192
+ end
193
+ self
194
+ end
195
+ ##
196
+ # Helper to move file to new path.
197
+ #
198
+ def move!(new_path)
184
199
  if exists?
185
200
  FileUtils.mv(path, new_path) unless new_path == path
186
201
  else
187
202
  File.open(new_path, "wb") { |f| f.write(read) }
188
203
  end
189
- chmod!(new_path, permissions)
190
- self.file = new_path
191
- self
192
204
  end
193
205
 
194
206
  ##
@@ -209,13 +221,20 @@ module CarrierWave
209
221
  new_path = File.expand_path(new_path)
210
222
 
211
223
  mkdir!(new_path, directory_permissions)
224
+ copy!(new_path)
225
+ chmod!(new_path, permissions)
226
+ self.class.new({:tempfile => new_path, :content_type => content_type})
227
+ end
228
+
229
+ ##
230
+ # Helper to create copy of file in new path.
231
+ #
232
+ def copy!(new_path)
212
233
  if exists?
213
234
  FileUtils.cp(path, new_path) unless new_path == path
214
235
  else
215
236
  File.open(new_path, "wb") { |f| f.write(read) }
216
237
  end
217
- chmod!(new_path, permissions)
218
- self.class.new({:tempfile => new_path, :content_type => content_type})
219
238
  end
220
239
 
221
240
  ##
@@ -245,10 +264,12 @@ module CarrierWave
245
264
  # [String] the content type of the file
246
265
  #
247
266
  def content_type
248
- @content_type ||=
249
- existing_content_type ||
250
- mime_magic_content_type ||
251
- mime_types_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
273
  end
253
274
 
254
275
  ##
@@ -279,7 +300,7 @@ module CarrierWave
279
300
  if file.is_a?(Hash)
280
301
  @file = file["tempfile"] || file[:tempfile]
281
302
  @original_filename = file["filename"] || file[:filename]
282
- @content_type = file["content_type"] || file[:content_type]
303
+ @content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
283
304
  else
284
305
  @file = file
285
306
  @original_filename = nil
@@ -291,7 +312,7 @@ module CarrierWave
291
312
  def mkdir!(path, directory_permissions)
292
313
  options = {}
293
314
  options[:mode] = directory_permissions if directory_permissions
294
- FileUtils.mkdir_p(File.dirname(path), options) unless File.exists?(File.dirname(path))
315
+ FileUtils.mkdir_p(File.dirname(path), options) unless File.exist?(File.dirname(path))
295
316
  end
296
317
 
297
318
  def chmod!(path, permissions)
@@ -300,7 +321,7 @@ module CarrierWave
300
321
 
301
322
  # Sanitize the filename, to prevent hacking
302
323
  def sanitize(name)
303
- name = name.gsub("\\", "/") # work-around for IE
324
+ name = name.tr("\\", "/") # work-around for IE
304
325
  name = File.basename(name)
305
326
  name = name.gsub(sanitize_regexp,"_")
306
327
  name = "_#{name}" if name =~ /\A\.+\z/
@@ -308,22 +329,6 @@ module CarrierWave
308
329
  return name.mb_chars.to_s
309
330
  end
310
331
 
311
- def existing_content_type
312
- if @file.respond_to?(:content_type) && @file.content_type
313
- @file.content_type.to_s.chomp
314
- end
315
- end
316
-
317
- def mime_magic_content_type
318
- MimeMagic.by_magic(File.open(path)).try(:type) if path
319
- rescue Errno::ENOENT
320
- nil
321
- end
322
-
323
- def mime_types_content_type
324
- ::MIME::Types.type_for(path).first.to_s if path
325
- end
326
-
327
332
  def split_extension(filename)
328
333
  # regular expressions to try for identifying extensions
329
334
  extension_matchers = [
@@ -1,11 +1,2 @@
1
1
  require "carrierwave/storage/abstract"
2
2
  require "carrierwave/storage/file"
3
-
4
- %w(aws google openstack rackspace).each do |fog_dependency|
5
- begin
6
- require "fog/#{fog_dependency}"
7
- rescue LoadError
8
- end
9
- end
10
-
11
- require "carrierwave/storage/fog" if defined?(Fog)
@@ -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
 
@@ -51,6 +49,70 @@ module CarrierWave
51
49
  CarrierWave::SanitizedFile.new(path)
52
50
  end
53
51
 
52
+ ##
53
+ # Stores given file to cache directory.
54
+ #
55
+ # === Parameters
56
+ #
57
+ # [new_file (File, IOString, Tempfile)] any kind of file object
58
+ #
59
+ # === Returns
60
+ #
61
+ # [CarrierWave::SanitizedFile] a sanitized file
62
+ #
63
+ def cache!(new_file)
64
+ new_file.move_to(::File.expand_path(uploader.cache_path, uploader.root), uploader.permissions, uploader.directory_permissions, true)
65
+ rescue Errno::EMLINK => e
66
+ raise(e) if @cache_called
67
+ @cache_called = true
68
+
69
+ # NOTE: Remove cached files older than 10 minutes
70
+ clean_cache!(600)
71
+
72
+ cache!(new_file)
73
+ end
74
+
75
+ ##
76
+ # Retrieves the file with the given cache_name from the cache.
77
+ #
78
+ # === Parameters
79
+ #
80
+ # [cache_name (String)] uniquely identifies a cache file
81
+ #
82
+ # === Raises
83
+ #
84
+ # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
85
+ #
86
+ def retrieve_from_cache!(identifier)
87
+ CarrierWave::SanitizedFile.new(::File.expand_path(uploader.cache_path(identifier), uploader.root))
88
+ end
89
+
90
+ ##
91
+ # Deletes a cache dir
92
+ #
93
+ def delete_dir!(path)
94
+ if path
95
+ begin
96
+ Dir.rmdir(::File.expand_path(path, uploader.root))
97
+ rescue Errno::ENOENT
98
+ # Ignore: path does not exist
99
+ rescue Errno::ENOTDIR
100
+ # Ignore: path is not a dir
101
+ rescue Errno::ENOTEMPTY, Errno::EEXIST
102
+ # Ignore: dir is not empty
103
+ end
104
+ end
105
+ end
106
+
107
+ def clean_cache!(seconds)
108
+ Dir.glob(::File.expand_path(::File.join(uploader.cache_dir, '*'), CarrierWave.root)).each do |dir|
109
+ time = dir.scan(/(\d+)-\d+-\d+$/).first.map(&:to_i)
110
+ time = Time.at(*time)
111
+ if time < (Time.now.utc - seconds)
112
+ FileUtils.rm_rf(dir)
113
+ end
114
+ end
115
+ end
54
116
  end # File
55
117
  end # Storage
56
118
  end # CarrierWave
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Storage
5
3
 
@@ -25,7 +23,7 @@ module CarrierWave
25
23
  # [:aws_access_key_id]
26
24
  # [:aws_secret_access_key]
27
25
  # [:region] (optional) defaults to 'us-east-1'
28
- # :region should be one of ['eu-west-1', 'us-east-1', 'ap-southeast-1', 'us-west-1', 'ap-northeast-1']
26
+ # :region should be one of ['eu-west-1', 'us-east-1', 'ap-southeast-1', 'us-west-1', 'ap-northeast-1', 'eu-central-1']
29
27
  #
30
28
  #
31
29
  # Google credentials contain the following keys:
@@ -94,6 +92,56 @@ module CarrierWave
94
92
  CarrierWave::Storage::Fog::File.new(uploader, self, uploader.store_path(identifier))
95
93
  end
96
94
 
95
+ ##
96
+ # Stores given file to cache directory.
97
+ #
98
+ # === Parameters
99
+ #
100
+ # [new_file (File, IOString, Tempfile)] any kind of file object
101
+ #
102
+ # === Returns
103
+ #
104
+ # [CarrierWave::SanitizedFile] a sanitized file
105
+ #
106
+ def cache!(new_file)
107
+ f = CarrierWave::Storage::Fog::File.new(uploader, self, uploader.cache_path)
108
+ f.store(new_file)
109
+ f
110
+ end
111
+
112
+ ##
113
+ # Retrieves the file with the given cache_name from the cache.
114
+ #
115
+ # === Parameters
116
+ #
117
+ # [cache_name (String)] uniquely identifies a cache file
118
+ #
119
+ # === Raises
120
+ #
121
+ # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
122
+ #
123
+ def retrieve_from_cache!(identifier)
124
+ CarrierWave::Storage::Fog::File.new(uploader, self, uploader.cache_path(identifier))
125
+ end
126
+
127
+ ##
128
+ # Deletes a cache dir
129
+ #
130
+ def delete_dir!(path)
131
+ # do nothing, because there's no such things as 'empty directory'
132
+ end
133
+
134
+ def clean_cache!(seconds)
135
+ connection.directories.new(
136
+ :key => uploader.fog_directory,
137
+ :public => uploader.fog_public
138
+ ).files.all(:prefix => uploader.cache_dir).each do |file|
139
+ time = file.key.scan(/(\d+)-\d+-\d+/).first.map { |t| t.to_i }
140
+ time = Time.at(*time)
141
+ file.destroy if time < (Time.now.utc - seconds)
142
+ end
143
+ end
144
+
97
145
  def connection
98
146
  @connection ||= begin
99
147
  options = credentials = uploader.fog_credentials
@@ -139,15 +187,17 @@ module CarrierWave
139
187
  # avoid a get by using local references
140
188
  local_directory = connection.directories.new(:key => @uploader.fog_directory)
141
189
  local_file = local_directory.files.new(:key => path)
142
- if @uploader.fog_credentials[:provider] == "AWS"
143
- local_file.url(::Fog::Time.now + @uploader.fog_authenticated_url_expiration, options)
144
- elsif ['Rackspace', 'OpenStack'].include?(@uploader.fog_credentials[:provider])
145
- connection.get_object_https_url(@uploader.fog_directory, path, ::Fog::Time.now + @uploader.fog_authenticated_url_expiration)
146
- else
147
- local_file.url(::Fog::Time.now + @uploader.fog_authenticated_url_expiration)
190
+ expire_at = ::Fog::Time.now + @uploader.fog_authenticated_url_expiration
191
+ case @uploader.fog_credentials[:provider]
192
+ when 'AWS'
193
+ local_file.url(expire_at, options)
194
+ when 'Rackspace'
195
+ connection.get_object_https_url(@uploader.fog_directory, path, expire_at, options)
196
+ when 'OpenStack'
197
+ connection.get_object_https_url(@uploader.fog_directory, path, expire_at)
198
+ else
199
+ local_file.url(expire_at)
148
200
  end
149
- else
150
- nil
151
201
  end
152
202
  end
153
203
 
@@ -159,7 +209,7 @@ module CarrierWave
159
209
  # [String] value of content-type
160
210
  #
161
211
  def content_type
162
- @content_type || file.content_type
212
+ @content_type || !file.nil? && file.content_type
163
213
  end
164
214
 
165
215
  ##
@@ -234,7 +284,7 @@ module CarrierWave
234
284
  # [Integer] size of file body
235
285
  #
236
286
  def size
237
- file.content_length
287
+ file.nil? ? 0 : file.content_length
238
288
  end
239
289
 
240
290
  ##
@@ -254,15 +304,19 @@ module CarrierWave
254
304
  #
255
305
  # [Boolean] true on success or raises error
256
306
  def store(new_file)
257
- fog_file = new_file.to_file
258
- @content_type ||= new_file.content_type
259
- @file = directory.files.create({
260
- :body => fog_file ? fog_file : new_file.read,
261
- :content_type => @content_type,
262
- :key => path,
263
- :public => @uploader.fog_public
264
- }.merge(@uploader.fog_attributes))
265
- fog_file.close if fog_file && !fog_file.closed?
307
+ if new_file.is_a?(self.class)
308
+ new_file.copy_to(path)
309
+ else
310
+ fog_file = new_file.to_file
311
+ @content_type ||= new_file.content_type
312
+ @file = directory.files.create({
313
+ :body => (fog_file ? fog_file : new_file).read,
314
+ :content_type => @content_type,
315
+ :key => path,
316
+ :public => @uploader.fog_public
317
+ }.merge(@uploader.fog_attributes))
318
+ fog_file.close if fog_file && !fog_file.closed?
319
+ end
266
320
  true
267
321
  end
268
322
 
@@ -285,7 +339,7 @@ module CarrierWave
285
339
  end
286
340
  else
287
341
  # AWS/Google optimized for speed over correctness
288
- case @uploader.fog_credentials[:provider]
342
+ case @uploader.fog_credentials[:provider].to_s
289
343
  when 'AWS'
290
344
  # check if some endpoint is set in fog_credentials
291
345
  if @uploader.fog_credentials.has_key?(:endpoint)
@@ -301,7 +355,8 @@ module CarrierWave
301
355
  end
302
356
  end
303
357
  when 'Google'
304
- "https://commondatastorage.googleapis.com/#{@uploader.fog_directory}/#{encoded_path}"
358
+ file = directory.files.get(encoded_path)
359
+ file.nil?? "" : file.public_url
305
360
  else
306
361
  # avoid a get by just using local reference
307
362
  directory.files.new(:key => path).public_url
@@ -336,9 +391,24 @@ module CarrierWave
336
391
  # [NilClass] no file name available
337
392
  #
338
393
  def filename(options = {})
339
- if file_url = url(options)
340
- URI.decode(file_url).gsub(/.*\/(.*?$)/, '\1').split('?').first
341
- end
394
+ return unless file_url = url(options)
395
+ CGI.unescape(file_url.split('?').first).gsub(/.*\/(.*?$)/, '\1')
396
+ end
397
+
398
+ ##
399
+ # Creates a copy of this file and returns it.
400
+ #
401
+ # === Parameters
402
+ #
403
+ # [new_path (String)] The path where the file should be copied to.
404
+ #
405
+ # === Returns
406
+ #
407
+ # @return [CarrierWave::Storage::Fog::File] the location where the file will be stored.
408
+ #
409
+ def copy_to(new_path)
410
+ connection.copy_object(@uploader.fog_directory, file.key, @uploader.fog_directory, new_path, acl_header)
411
+ CarrierWave::Storage::Fog::File.new(@uploader, @base, new_path)
342
412
  end
343
413
 
344
414
  private
@@ -381,6 +451,9 @@ module CarrierWave
381
451
  @file ||= directory.files.head(path)
382
452
  end
383
453
 
454
+ def acl_header
455
+ {'x-amz-acl' => @uploader.fog_public ? 'public-read' : 'private'}
456
+ end
384
457
  end
385
458
 
386
459
  end # Fog