carrierwave 0.11.2 → 2.1.0

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 (60) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +294 -124
  3. data/lib/carrierwave.rb +34 -8
  4. data/lib/carrierwave/compatibility/paperclip.rb +0 -2
  5. data/lib/carrierwave/downloader/base.rb +50 -0
  6. data/lib/carrierwave/downloader/remote_file.rb +44 -0
  7. data/lib/carrierwave/error.rb +1 -0
  8. data/lib/carrierwave/locale/en.yml +7 -4
  9. data/lib/carrierwave/mount.rb +229 -180
  10. data/lib/carrierwave/mounter.rb +188 -0
  11. data/lib/carrierwave/orm/activerecord.rb +59 -24
  12. data/lib/carrierwave/processing.rb +0 -1
  13. data/lib/carrierwave/processing/mini_magick.rb +137 -83
  14. data/lib/carrierwave/processing/rmagick.rb +54 -5
  15. data/lib/carrierwave/sanitized_file.rb +50 -30
  16. data/lib/carrierwave/storage.rb +1 -9
  17. data/lib/carrierwave/storage/abstract.rb +15 -2
  18. data/lib/carrierwave/storage/file.rb +69 -2
  19. data/lib/carrierwave/storage/fog.rb +177 -37
  20. data/lib/carrierwave/test/matchers.rb +77 -12
  21. data/lib/carrierwave/uploader.rb +2 -2
  22. data/lib/carrierwave/uploader/cache.rb +60 -38
  23. data/lib/carrierwave/uploader/callbacks.rb +0 -2
  24. data/lib/carrierwave/uploader/configuration.rb +71 -13
  25. data/lib/carrierwave/uploader/content_type_whitelist.rb +1 -1
  26. data/lib/carrierwave/uploader/default_url.rb +3 -5
  27. data/lib/carrierwave/uploader/download.rb +4 -74
  28. data/lib/carrierwave/uploader/extension_blacklist.rb +14 -10
  29. data/lib/carrierwave/uploader/extension_whitelist.rb +13 -10
  30. data/lib/carrierwave/uploader/file_size.rb +43 -0
  31. data/lib/carrierwave/uploader/mountable.rb +13 -8
  32. data/lib/carrierwave/uploader/processing.rb +10 -10
  33. data/lib/carrierwave/uploader/proxy.rb +6 -8
  34. data/lib/carrierwave/uploader/remove.rb +0 -2
  35. data/lib/carrierwave/uploader/serialization.rb +2 -4
  36. data/lib/carrierwave/uploader/store.rb +17 -24
  37. data/lib/carrierwave/uploader/url.rb +3 -5
  38. data/lib/carrierwave/uploader/versions.rb +123 -93
  39. data/lib/carrierwave/utilities.rb +0 -3
  40. data/lib/carrierwave/utilities/uri.rb +5 -6
  41. data/lib/carrierwave/validations/active_model.rb +3 -5
  42. data/lib/carrierwave/version.rb +1 -1
  43. data/lib/generators/templates/uploader.rb +4 -8
  44. metadata +80 -65
  45. data/lib/carrierwave/locale/cs.yml +0 -11
  46. data/lib/carrierwave/locale/de.yml +0 -11
  47. data/lib/carrierwave/locale/el.yml +0 -11
  48. data/lib/carrierwave/locale/es.yml +0 -11
  49. data/lib/carrierwave/locale/fr.yml +0 -11
  50. data/lib/carrierwave/locale/ja.yml +0 -11
  51. data/lib/carrierwave/locale/nb.yml +0 -11
  52. data/lib/carrierwave/locale/nl.yml +0 -11
  53. data/lib/carrierwave/locale/pl.yml +0 -11
  54. data/lib/carrierwave/locale/pt-BR.yml +0 -11
  55. data/lib/carrierwave/locale/pt-PT.yml +0 -11
  56. data/lib/carrierwave/locale/ru.yml +0 -11
  57. data/lib/carrierwave/locale/sk.yml +0 -11
  58. data/lib/carrierwave/locale/tr.yml +0 -11
  59. data/lib/carrierwave/processing/mime_types.rb +0 -74
  60. data/lib/carrierwave/utilities/deprecation.rb +0 -18
@@ -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,6 +224,8 @@ 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
231
  new_img = ::Magick::Image.new(width, height) { self.background_color = background == :transparent ? 'rgba(255,255,255,0)' : background.to_s }
@@ -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
@@ -318,20 +353,25 @@ module CarrierWave
318
353
  frames = ::Magick::ImageList.new
319
354
 
320
355
  image.each_with_index do |frame, index|
321
- frame = yield *[frame, index, options].take(block.arity) if block_given?
356
+ frame = yield(*[frame, index, options].take(block.arity)) if block_given?
322
357
  frames << frame if frame
323
358
  end
324
359
  frames.append(true) if block_given?
325
360
 
326
361
  write_block = create_info_block(options[:write])
362
+
327
363
  if options[:format] || @format
328
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)
329
368
  else
330
369
  frames.write(current_path, &write_block)
331
370
  end
371
+
332
372
  destroy_image(frames)
333
373
  rescue ::Magick::ImageMagickError => e
334
- 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)
335
375
  end
336
376
 
337
377
  private
@@ -344,7 +384,16 @@ module CarrierWave
344
384
  end
345
385
 
346
386
  def destroy_image(image)
347
- image.destroy! if image.respond_to?(:destroy!)
387
+ image.try(:destroy!)
388
+ end
389
+
390
+ def dimension_from(value)
391
+ return value unless value.instance_of?(Proc)
392
+ value.arity >= 1 ? value.call(self) : value.call
393
+ end
394
+
395
+ def rmagick_image
396
+ ::Magick::Image.from_blob(self.read).first
348
397
  end
349
398
 
350
399
  end # RMagick
@@ -1,8 +1,6 @@
1
- # encoding: utf-8
2
-
3
1
  require 'pathname'
4
2
  require 'active_support/core_ext/string/multibyte'
5
- require 'mime/types'
3
+ require 'mini_mime'
6
4
  require 'mimemagic'
7
5
 
8
6
  module CarrierWave
@@ -17,18 +15,19 @@ module CarrierWave
17
15
  #
18
16
  class SanitizedFile
19
17
 
20
- attr_accessor :file
18
+ attr_reader :file
21
19
 
22
20
  class << self
23
21
  attr_writer :sanitize_regexp
24
22
 
25
23
  def sanitize_regexp
26
- @sanitize_regexp ||= /[^a-zA-Z0-9\.\-\+_]/
24
+ @sanitize_regexp ||= /[^[:word:]\.\-\+]/
27
25
  end
28
26
  end
29
27
 
30
28
  def initialize(file)
31
29
  self.file = file
30
+ @content = nil
32
31
  end
33
32
 
34
33
  ##
@@ -110,12 +109,11 @@ module CarrierWave
110
109
  # [String, nil] the path where the file is located.
111
110
  #
112
111
  def path
113
- unless @file.blank?
114
- if is_path?
115
- File.expand_path(@file)
116
- elsif @file.respond_to?(:path) and not @file.path.blank?
117
- File.expand_path(@file.path)
118
- 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)
119
117
  end
120
118
  end
121
119
 
@@ -143,8 +141,7 @@ module CarrierWave
143
141
  # [Boolean] Whether the file exists
144
142
  #
145
143
  def exists?
146
- return File.exists?(self.path) if self.path
147
- return false
144
+ self.path.present? && File.exist?(self.path)
148
145
  end
149
146
 
150
147
  ##
@@ -160,9 +157,9 @@ module CarrierWave
160
157
  elsif is_path?
161
158
  File.open(@file, "rb") {|file| file.read}
162
159
  else
163
- @file.rewind if @file.respond_to?(:rewind)
160
+ @file.try(:rewind)
164
161
  @content = @file.read
165
- @file.close if @file.respond_to?(:close) && @file.respond_to?(:closed?) && !@file.closed?
162
+ @file.try(:close) unless @file.try(:closed?)
166
163
  @content
167
164
  end
168
165
  end
@@ -176,19 +173,29 @@ module CarrierWave
176
173
  # [permissions (Integer)] permissions to set on the file in its new location.
177
174
  # [directory_permissions (Integer)] permissions to set on created directories.
178
175
  #
179
- def move_to(new_path, permissions=nil, directory_permissions=nil)
176
+ def move_to(new_path, permissions=nil, directory_permissions=nil, keep_filename=false)
180
177
  return if self.empty?
181
178
  new_path = File.expand_path(new_path)
182
179
 
183
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)
184
194
  if exists?
185
- FileUtils.mv(path, new_path) unless new_path == path
195
+ FileUtils.mv(path, new_path) unless File.identical?(new_path, path)
186
196
  else
187
197
  File.open(new_path, "wb") { |f| f.write(read) }
188
198
  end
189
- chmod!(new_path, permissions)
190
- self.file = new_path
191
- self
192
199
  end
193
200
 
194
201
  ##
@@ -209,13 +216,20 @@ module CarrierWave
209
216
  new_path = File.expand_path(new_path)
210
217
 
211
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)
212
228
  if exists?
213
229
  FileUtils.cp(path, new_path) unless new_path == path
214
230
  else
215
231
  File.open(new_path, "wb") { |f| f.write(read) }
216
232
  end
217
- chmod!(new_path, permissions)
218
- self.class.new({:tempfile => new_path, :content_type => content_type})
219
233
  end
220
234
 
221
235
  ##
@@ -248,7 +262,7 @@ module CarrierWave
248
262
  @content_type ||=
249
263
  existing_content_type ||
250
264
  mime_magic_content_type ||
251
- mime_types_content_type
265
+ mini_mime_content_type
252
266
  end
253
267
 
254
268
  ##
@@ -279,7 +293,7 @@ module CarrierWave
279
293
  if file.is_a?(Hash)
280
294
  @file = file["tempfile"] || file[:tempfile]
281
295
  @original_filename = file["filename"] || file[:filename]
282
- @content_type = file["content_type"] || file[:content_type]
296
+ @content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
283
297
  else
284
298
  @file = file
285
299
  @original_filename = nil
@@ -291,7 +305,7 @@ module CarrierWave
291
305
  def mkdir!(path, directory_permissions)
292
306
  options = {}
293
307
  options[:mode] = directory_permissions if directory_permissions
294
- 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))
295
309
  end
296
310
 
297
311
  def chmod!(path, permissions)
@@ -300,11 +314,11 @@ module CarrierWave
300
314
 
301
315
  # Sanitize the filename, to prevent hacking
302
316
  def sanitize(name)
303
- name = name.gsub("\\", "/") # work-around for IE
317
+ name = name.tr("\\", "/") # work-around for IE
304
318
  name = File.basename(name)
305
319
  name = name.gsub(sanitize_regexp,"_")
306
320
  name = "_#{name}" if name =~ /\A\.+\z/
307
- name = "unnamed" if name.size == 0
321
+ name = "unnamed" if name.size.zero?
308
322
  return name.mb_chars.to_s
309
323
  end
310
324
 
@@ -315,13 +329,19 @@ module CarrierWave
315
329
  end
316
330
 
317
331
  def mime_magic_content_type
318
- MimeMagic.by_magic(File.open(path)).try(:type) if path
332
+ if path
333
+ File.open(path) do |file|
334
+ MimeMagic.by_magic(file).try(:type) || 'invalid/invalid'
335
+ end
336
+ end
319
337
  rescue Errno::ENOENT
320
338
  nil
321
339
  end
322
340
 
323
- def mime_types_content_type
324
- ::MIME::Types.type_for(path).first.to_s if path
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
325
345
  end
326
346
 
327
347
  def split_extension(filename)
@@ -1,11 +1,3 @@
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)
3
+ require "carrierwave/storage/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
 
@@ -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
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Storage
5
3
 
@@ -18,6 +16,8 @@ module CarrierWave
18
16
  # [:fog_authenticated_url_expiration] (optional) time (in seconds) that authenticated urls
19
17
  # will be valid, when fog_public is false and provider is AWS or Google, defaults to 600
20
18
  # [:fog_use_ssl_for_aws] (optional) #public_url will use https for the AWS generated URL]
19
+ # [:fog_aws_accelerate] (optional) #public_url will use s3-accelerate subdomain
20
+ # instead of s3, defaults to false
21
21
  #
22
22
  #
23
23
  # AWS credentials contain the following keys:
@@ -25,7 +25,7 @@ module CarrierWave
25
25
  # [:aws_access_key_id]
26
26
  # [:aws_secret_access_key]
27
27
  # [: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']
28
+ # :region should be one of ['eu-west-1', 'us-east-1', 'ap-southeast-1', 'us-west-1', 'ap-northeast-1', 'eu-central-1']
29
29
  #
30
30
  #
31
31
  # Google credentials contain the following keys:
@@ -60,6 +60,14 @@ module CarrierWave
60
60
  def connection_cache
61
61
  @connection_cache ||= {}
62
62
  end
63
+
64
+ def eager_load
65
+ # see #1198. This will hopefully no longer be necessary in future release of fog
66
+ fog_credentials = CarrierWave::Uploader::Base.fog_credentials
67
+ if fog_credentials.present?
68
+ CarrierWave::Storage::Fog.connection_cache[fog_credentials] ||= ::Fog::Storage.new(fog_credentials)
69
+ end
70
+ end
63
71
  end
64
72
 
65
73
  ##
@@ -94,6 +102,57 @@ module CarrierWave
94
102
  CarrierWave::Storage::Fog::File.new(uploader, self, uploader.store_path(identifier))
95
103
  end
96
104
 
105
+ ##
106
+ # Stores given file to cache directory.
107
+ #
108
+ # === Parameters
109
+ #
110
+ # [new_file (File, IOString, Tempfile)] any kind of file object
111
+ #
112
+ # === Returns
113
+ #
114
+ # [CarrierWave::SanitizedFile] a sanitized file
115
+ #
116
+ def cache!(new_file)
117
+ f = CarrierWave::Storage::Fog::File.new(uploader, self, uploader.cache_path)
118
+ f.store(new_file)
119
+ f
120
+ end
121
+
122
+ ##
123
+ # Retrieves the file with the given cache_name from the cache.
124
+ #
125
+ # === Parameters
126
+ #
127
+ # [cache_name (String)] uniquely identifies a cache file
128
+ #
129
+ # === Raises
130
+ #
131
+ # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
132
+ #
133
+ def retrieve_from_cache!(identifier)
134
+ CarrierWave::Storage::Fog::File.new(uploader, self, uploader.cache_path(identifier))
135
+ end
136
+
137
+ ##
138
+ # Deletes a cache dir
139
+ #
140
+ def delete_dir!(path)
141
+ # do nothing, because there's no such things as 'empty directory'
142
+ end
143
+
144
+ def clean_cache!(seconds)
145
+ connection.directories.new(
146
+ :key => uploader.fog_directory,
147
+ :public => uploader.fog_public
148
+ ).files.all(:prefix => uploader.cache_dir).each do |file|
149
+ # generate_cache_id returns key formated TIMEINT-PID(-COUNTER)-RND
150
+ time = file.key.scan(/(\d+)-\d+-\d+(?:-\d+)?/).first.map { |t| t.to_i }
151
+ time = Time.at(*time)
152
+ file.destroy if time < (Time.now.utc - seconds)
153
+ end
154
+ end
155
+
97
156
  def connection
98
157
  @connection ||= begin
99
158
  options = credentials = uploader.fog_credentials
@@ -102,6 +161,8 @@ module CarrierWave
102
161
  end
103
162
 
104
163
  class File
164
+ DEFAULT_S3_REGION = 'us-east-1'
165
+
105
166
  include CarrierWave::Utilities::Uri
106
167
 
107
168
  ##
@@ -126,7 +187,7 @@ module CarrierWave
126
187
 
127
188
  ##
128
189
  # Return a temporary authenticated url to a private file, if available
129
- # Only supported for AWS, Rackspace and Google providers
190
+ # Only supported for AWS, Rackspace, Google, AzureRM and Aliyun providers
130
191
  #
131
192
  # === Returns
132
193
  #
@@ -135,19 +196,28 @@ module CarrierWave
135
196
  # [NilClass] no authenticated url available
136
197
  #
137
198
  def authenticated_url(options = {})
138
- if ['AWS', 'Google', 'Rackspace', 'OpenStack'].include?(@uploader.fog_credentials[:provider])
199
+ if ['AWS', 'Google', 'Rackspace', 'OpenStack', 'AzureRM', 'Aliyun', 'backblaze'].include?(@uploader.fog_credentials[:provider])
139
200
  # avoid a get by using local references
140
201
  local_directory = connection.directories.new(:key => @uploader.fog_directory)
141
202
  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)
203
+ expire_at = options[:expire_at] || ::Fog::Time.now + @uploader.fog_authenticated_url_expiration
204
+ case @uploader.fog_credentials[:provider]
205
+ when 'AWS', 'Google'
206
+ # Older versions of fog-google do not support options as a parameter
207
+ if url_options_supported?(local_file)
208
+ local_file.url(expire_at, options)
209
+ else
210
+ warn "Options hash not supported in #{local_file.class}. You may need to upgrade your Fog provider."
211
+ local_file.url(expire_at)
212
+ end
213
+ when 'Rackspace', 'OpenStack'
214
+ connection.get_object_https_url(@uploader.fog_directory, path, expire_at, options)
215
+ when 'Aliyun'
216
+ expire_at = expire_at - Time.now
217
+ local_file.url(expire_at)
218
+ else
219
+ local_file.url(expire_at)
148
220
  end
149
- else
150
- nil
151
221
  end
152
222
  end
153
223
 
@@ -159,7 +229,7 @@ module CarrierWave
159
229
  # [String] value of content-type
160
230
  #
161
231
  def content_type
162
- @content_type || file.content_type
232
+ @content_type || file.try(:content_type)
163
233
  end
164
234
 
165
235
  ##
@@ -182,7 +252,9 @@ module CarrierWave
182
252
  #
183
253
  def delete
184
254
  # avoid a get by just using local reference
185
- directory.files.new(:key => path).destroy
255
+ directory.files.new(:key => path).destroy.tap do |result|
256
+ @file = nil if result
257
+ end
186
258
  end
187
259
 
188
260
  ##
@@ -213,7 +285,7 @@ module CarrierWave
213
285
  end
214
286
 
215
287
  def initialize(uploader, base, path)
216
- @uploader, @base, @path = uploader, base, path
288
+ @uploader, @base, @path, @content_type = uploader, base, path, nil
217
289
  end
218
290
 
219
291
  ##
@@ -223,6 +295,16 @@ module CarrierWave
223
295
  #
224
296
  # [String] contents of file
225
297
  def read
298
+ file_body = file.body
299
+
300
+ return if file_body.nil?
301
+ return file_body unless file_body.is_a?(::File)
302
+
303
+ # Fog::Storage::XXX::File#body could return the source file which was upoloaded to the remote server.
304
+ read_source_file(file_body) if ::File.exist?(file_body.path)
305
+
306
+ # If the source file doesn't exist, the remote content is read
307
+ @file = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
226
308
  file.body
227
309
  end
228
310
 
@@ -234,7 +316,7 @@ module CarrierWave
234
316
  # [Integer] size of file body
235
317
  #
236
318
  def size
237
- file.content_length
319
+ file.nil? ? 0 : file.content_length
238
320
  end
239
321
 
240
322
  ##
@@ -244,7 +326,7 @@ module CarrierWave
244
326
  #
245
327
  # [Boolean] true if file exists or false
246
328
  def exists?
247
- !!directory.files.head(path)
329
+ !!file
248
330
  end
249
331
 
250
332
  ##
@@ -254,15 +336,19 @@ module CarrierWave
254
336
  #
255
337
  # [Boolean] true on success or raises error
256
338
  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?
339
+ if new_file.is_a?(self.class)
340
+ new_file.copy_to(path)
341
+ else
342
+ fog_file = new_file.to_file
343
+ @content_type ||= new_file.content_type
344
+ @file = directory.files.create({
345
+ :body => fog_file ? fog_file : new_file.read,
346
+ :content_type => @content_type,
347
+ :key => path,
348
+ :public => @uploader.fog_public
349
+ }.merge(@uploader.fog_attributes))
350
+ fog_file.close if fog_file && !fog_file.closed?
351
+ end
266
352
  true
267
353
  end
268
354
 
@@ -285,23 +371,35 @@ module CarrierWave
285
371
  end
286
372
  else
287
373
  # AWS/Google optimized for speed over correctness
288
- case @uploader.fog_credentials[:provider]
374
+ case fog_provider
289
375
  when 'AWS'
290
376
  # check if some endpoint is set in fog_credentials
291
377
  if @uploader.fog_credentials.has_key?(:endpoint)
292
378
  "#{@uploader.fog_credentials[:endpoint]}/#{@uploader.fog_directory}/#{encoded_path}"
293
379
  else
294
380
  protocol = @uploader.fog_use_ssl_for_aws ? "https" : "http"
381
+
382
+ subdomain_regex = /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9\.]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
383
+ valid_subdomain = @uploader.fog_directory.to_s =~ subdomain_regex && !(protocol == 'https' && @uploader.fog_directory =~ /\./)
384
+
295
385
  # if directory is a valid subdomain, use that style for access
296
- if @uploader.fog_directory.to_s =~ /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9\.]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
297
- "#{protocol}://#{@uploader.fog_directory}.s3.amazonaws.com/#{encoded_path}"
298
- else
299
- # directory is not a valid subdomain, so use path style for access
300
- "#{protocol}://s3.amazonaws.com/#{@uploader.fog_directory}/#{encoded_path}"
386
+ if valid_subdomain
387
+ s3_subdomain = @uploader.fog_aws_accelerate ? "s3-accelerate" : "s3"
388
+ "#{protocol}://#{@uploader.fog_directory}.#{s3_subdomain}.amazonaws.com/#{encoded_path}"
389
+ else # directory is not a valid subdomain, so use path style for access
390
+ region = @uploader.fog_credentials[:region].to_s
391
+ host = case region
392
+ when DEFAULT_S3_REGION, ''
393
+ 's3.amazonaws.com'
394
+ else
395
+ "s3.#{region}.amazonaws.com"
396
+ end
397
+ "#{protocol}://#{host}/#{@uploader.fog_directory}/#{encoded_path}"
301
398
  end
302
399
  end
303
400
  when 'Google'
304
- "https://commondatastorage.googleapis.com/#{@uploader.fog_directory}/#{encoded_path}"
401
+ # https://cloud.google.com/storage/docs/access-public-data
402
+ "https://storage.googleapis.com/#{@uploader.fog_directory}/#{encoded_path}"
305
403
  else
306
404
  # avoid a get by just using local reference
307
405
  directory.files.new(:key => path).public_url
@@ -336,9 +434,24 @@ module CarrierWave
336
434
  # [NilClass] no file name available
337
435
  #
338
436
  def filename(options = {})
339
- if file_url = url(options)
340
- URI.decode(file_url).gsub(/.*\/(.*?$)/, '\1').split('?').first
341
- end
437
+ return unless file_url = url(options)
438
+ CGI.unescape(file_url.split('?').first).gsub(/.*\/(.*?$)/, '\1')
439
+ end
440
+
441
+ ##
442
+ # Creates a copy of this file and returns it.
443
+ #
444
+ # === Parameters
445
+ #
446
+ # [new_path (String)] The path where the file should be copied to.
447
+ #
448
+ # === Returns
449
+ #
450
+ # @return [CarrierWave::Storage::Fog::File] the location where the file will be stored.
451
+ #
452
+ def copy_to(new_path)
453
+ connection.copy_object(@uploader.fog_directory, file.key, @uploader.fog_directory, new_path, acl_header)
454
+ CarrierWave::Storage::Fog::File.new(@uploader, @base, new_path)
342
455
  end
343
456
 
344
457
  private
@@ -381,6 +494,33 @@ module CarrierWave
381
494
  @file ||= directory.files.head(path)
382
495
  end
383
496
 
497
+ def acl_header
498
+ if fog_provider == 'AWS'
499
+ { 'x-amz-acl' => @uploader.fog_public ? 'public-read' : 'private' }
500
+ else
501
+ {}
502
+ end
503
+ end
504
+
505
+ def fog_provider
506
+ @uploader.fog_credentials[:provider].to_s
507
+ end
508
+
509
+ def read_source_file(file_body)
510
+ return unless ::File.exist?(file_body.path)
511
+
512
+ begin
513
+ file_body = ::File.open(file_body.path) if file_body.closed? # Reopen if it's already closed
514
+ file_body.read
515
+ ensure
516
+ file_body.close
517
+ end
518
+ end
519
+
520
+ def url_options_supported?(local_file)
521
+ parameters = local_file.method(:url).parameters
522
+ parameters.count == 2 && parameters[1].include?(:options)
523
+ end
384
524
  end
385
525
 
386
526
  end # Fog