carrierwave 1.3.0 → 2.2.3
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.
- checksums.yaml +4 -4
- data/README.md +120 -43
- data/lib/carrierwave/downloader/base.rb +93 -0
- data/lib/carrierwave/downloader/remote_file.rb +65 -0
- data/lib/carrierwave/locale/en.yml +5 -4
- data/lib/carrierwave/mount.rb +25 -19
- data/lib/carrierwave/mounter.rb +71 -48
- data/lib/carrierwave/orm/activerecord.rb +14 -8
- data/lib/carrierwave/processing/mini_magick.rb +100 -117
- data/lib/carrierwave/processing/rmagick.rb +11 -5
- data/lib/carrierwave/processing/vips.rb +284 -0
- data/lib/carrierwave/processing.rb +1 -0
- data/lib/carrierwave/sanitized_file.rb +44 -23
- data/lib/carrierwave/storage/file.rb +2 -2
- data/lib/carrierwave/storage/fog.rb +46 -17
- data/lib/carrierwave/storage.rb +1 -0
- data/lib/carrierwave/uploader/cache.rb +24 -16
- data/lib/carrierwave/uploader/configuration.rb +28 -15
- data/lib/carrierwave/uploader/content_type_blacklist.rb +17 -8
- data/lib/carrierwave/uploader/content_type_whitelist.rb +20 -8
- data/lib/carrierwave/uploader/download.rb +2 -80
- data/lib/carrierwave/uploader/extension_blacklist.rb +18 -10
- data/lib/carrierwave/uploader/extension_whitelist.rb +19 -10
- data/lib/carrierwave/uploader/mountable.rb +6 -0
- data/lib/carrierwave/uploader/proxy.rb +2 -2
- data/lib/carrierwave/uploader/serialization.rb +1 -1
- data/lib/carrierwave/uploader/store.rb +5 -3
- data/lib/carrierwave/uploader/url.rb +6 -3
- data/lib/carrierwave/uploader/versions.rb +43 -13
- data/lib/carrierwave/uploader.rb +0 -9
- data/lib/carrierwave/validations/active_model.rb +3 -3
- data/lib/carrierwave/version.rb +1 -1
- data/lib/carrierwave.rb +4 -0
- data/lib/generators/templates/uploader.rb +2 -2
- metadata +113 -21
@@ -0,0 +1,284 @@
|
|
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
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Changes the image encoding format to the given format
|
85
|
+
#
|
86
|
+
# See https://libvips.github.io/libvips/API/current/using-cli.html#using-command-line-conversion
|
87
|
+
#
|
88
|
+
# === Parameters
|
89
|
+
#
|
90
|
+
# [format (#to_s)] an abbreviation of the format
|
91
|
+
#
|
92
|
+
# === Yields
|
93
|
+
#
|
94
|
+
# [Vips::Image] additional manipulations to perform
|
95
|
+
#
|
96
|
+
# === Examples
|
97
|
+
#
|
98
|
+
# image.convert(:png)
|
99
|
+
#
|
100
|
+
def convert(format, page=nil)
|
101
|
+
vips! do |builder|
|
102
|
+
builder = builder.convert(format)
|
103
|
+
builder = builder.loader(page: page) if page
|
104
|
+
builder
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Resize the image to fit within the specified dimensions while retaining
|
110
|
+
# the original aspect ratio. Will only resize the image if it is larger than the
|
111
|
+
# specified dimensions. The resulting image may be shorter or narrower than specified
|
112
|
+
# in the smaller dimension but will not be larger than the specified values.
|
113
|
+
#
|
114
|
+
# === Parameters
|
115
|
+
#
|
116
|
+
# [width (Integer)] the width to scale the image to
|
117
|
+
# [height (Integer)] the height to scale the image to
|
118
|
+
# [combine_options (Hash)] additional Vips options to apply before resizing
|
119
|
+
#
|
120
|
+
# === Yields
|
121
|
+
#
|
122
|
+
# [Vips::Image] additional manipulations to perform
|
123
|
+
#
|
124
|
+
def resize_to_limit(width, height, combine_options: {})
|
125
|
+
width, height = resolve_dimensions(width, height)
|
126
|
+
|
127
|
+
vips! do |builder|
|
128
|
+
builder.resize_to_limit(width, height)
|
129
|
+
.apply(combine_options)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Resize the image to fit within the specified dimensions while retaining
|
135
|
+
# the original aspect ratio. The image may be shorter or narrower than
|
136
|
+
# specified in the smaller dimension but will not be larger than the specified values.
|
137
|
+
#
|
138
|
+
# === Parameters
|
139
|
+
#
|
140
|
+
# [width (Integer)] the width to scale the image to
|
141
|
+
# [height (Integer)] the height to scale the image to
|
142
|
+
# [combine_options (Hash)] additional Vips options to apply before resizing
|
143
|
+
#
|
144
|
+
# === Yields
|
145
|
+
#
|
146
|
+
# [Vips::Image] additional manipulations to perform
|
147
|
+
#
|
148
|
+
def resize_to_fit(width, height, combine_options: {})
|
149
|
+
width, height = resolve_dimensions(width, height)
|
150
|
+
|
151
|
+
vips! do |builder|
|
152
|
+
builder.resize_to_fit(width, height)
|
153
|
+
.apply(combine_options)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# Resize the image to fit within the specified dimensions while retaining
|
159
|
+
# the aspect ratio of the original image. If necessary, crop the image in the
|
160
|
+
# larger dimension.
|
161
|
+
#
|
162
|
+
# === Parameters
|
163
|
+
#
|
164
|
+
# [width (Integer)] the width to scale the image to
|
165
|
+
# [height (Integer)] the height to scale the image to
|
166
|
+
# [combine_options (Hash)] additional vips options to apply before resizing
|
167
|
+
#
|
168
|
+
# === Yields
|
169
|
+
#
|
170
|
+
# [Vips::Image] additional manipulations to perform
|
171
|
+
#
|
172
|
+
def resize_to_fill(width, height, _gravity = nil, combine_options: {})
|
173
|
+
width, height = resolve_dimensions(width, height)
|
174
|
+
|
175
|
+
vips! do |builder|
|
176
|
+
builder.resize_to_fill(width, height).apply(combine_options)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Resize the image to fit within the specified dimensions while retaining
|
182
|
+
# the original aspect ratio. If necessary, will pad the remaining area
|
183
|
+
# with the given color, which defaults to transparent (for gif and png,
|
184
|
+
# white for jpeg).
|
185
|
+
#
|
186
|
+
# See https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsCompassDirection
|
187
|
+
# for gravity options.
|
188
|
+
#
|
189
|
+
# === Parameters
|
190
|
+
#
|
191
|
+
# [width (Integer)] the width to scale the image to
|
192
|
+
# [height (Integer)] the height to scale the image to
|
193
|
+
# [background (List, nil)] the color of the background as a RGB, like [0, 255, 255], nil indicates transparent
|
194
|
+
# [gravity (String)] how to position the image
|
195
|
+
# [alpha (Boolean, nil)] pad the image with the alpha channel if supported
|
196
|
+
# [combine_options (Hash)] additional vips options to apply before resizing
|
197
|
+
#
|
198
|
+
# === Yields
|
199
|
+
#
|
200
|
+
# [Vips::Image] additional manipulations to perform
|
201
|
+
#
|
202
|
+
def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil, combine_options: {})
|
203
|
+
width, height = resolve_dimensions(width, height)
|
204
|
+
|
205
|
+
vips! do |builder|
|
206
|
+
builder.resize_and_pad(width, height, background: background, gravity: gravity, alpha: alpha)
|
207
|
+
.apply(combine_options)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
##
|
212
|
+
# Returns the width of the image in pixels.
|
213
|
+
#
|
214
|
+
# === Returns
|
215
|
+
#
|
216
|
+
# [Integer] the image's width in pixels
|
217
|
+
#
|
218
|
+
def width
|
219
|
+
vips_image.width
|
220
|
+
end
|
221
|
+
|
222
|
+
##
|
223
|
+
# Returns the height of the image in pixels.
|
224
|
+
#
|
225
|
+
# === Returns
|
226
|
+
#
|
227
|
+
# [Integer] the image's height in pixels
|
228
|
+
#
|
229
|
+
def height
|
230
|
+
vips_image.height
|
231
|
+
end
|
232
|
+
|
233
|
+
# Process the image with vip, using the ImageProcessing gem. This
|
234
|
+
# method will build a "convert" vips command and execute it on the
|
235
|
+
# current image.
|
236
|
+
#
|
237
|
+
# === Gotcha
|
238
|
+
#
|
239
|
+
# This method assumes that the object responds to +current_path+.
|
240
|
+
# Any class that this module is mixed into must have a +current_path+ method.
|
241
|
+
# CarrierWave::Uploader does, so you won't need to worry about this in
|
242
|
+
# most cases.
|
243
|
+
#
|
244
|
+
# === Yields
|
245
|
+
#
|
246
|
+
# [ImageProcessing::Builder] use it to define processing to be performed
|
247
|
+
#
|
248
|
+
# === Raises
|
249
|
+
#
|
250
|
+
# [CarrierWave::ProcessingError] if processing failed.
|
251
|
+
def vips!
|
252
|
+
builder = ImageProcessing::Vips.source(current_path)
|
253
|
+
builder = yield(builder)
|
254
|
+
|
255
|
+
result = builder.call
|
256
|
+
result.close
|
257
|
+
|
258
|
+
FileUtils.mv result.path, current_path
|
259
|
+
|
260
|
+
if File.extname(result.path) != File.extname(current_path)
|
261
|
+
move_to = current_path.chomp(File.extname(current_path)) + File.extname(result.path)
|
262
|
+
file.content_type = ::MiniMime.lookup_by_filename(move_to).content_type
|
263
|
+
file.move_to(move_to, permissions, directory_permissions)
|
264
|
+
end
|
265
|
+
rescue ::Vips::Error => e
|
266
|
+
message = I18n.translate(:"errors.messages.vips_processing_error", :e => e)
|
267
|
+
raise CarrierWave::ProcessingError, message
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def resolve_dimensions(*dimensions)
|
273
|
+
dimensions.map do |value|
|
274
|
+
next value unless value.instance_of?(Proc)
|
275
|
+
value.arity >= 1 ? value.call(self) : value.call
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def vips_image
|
280
|
+
::Vips::Image.new_from_buffer(read, "")
|
281
|
+
end
|
282
|
+
|
283
|
+
end # Vips
|
284
|
+
end # CarrierWave
|
@@ -1,12 +1,7 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require 'active_support/core_ext/string/multibyte'
|
3
|
-
|
4
|
-
|
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
|
3
|
+
require 'mini_mime'
|
4
|
+
require 'marcel'
|
10
5
|
|
11
6
|
module CarrierWave
|
12
7
|
|
@@ -114,12 +109,11 @@ module CarrierWave
|
|
114
109
|
# [String, nil] the path where the file is located.
|
115
110
|
#
|
116
111
|
def path
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
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)
|
123
117
|
end
|
124
118
|
end
|
125
119
|
|
@@ -187,9 +181,9 @@ module CarrierWave
|
|
187
181
|
move!(new_path)
|
188
182
|
chmod!(new_path, permissions)
|
189
183
|
if keep_filename
|
190
|
-
self.file = {:tempfile => new_path, :filename => original_filename, :content_type => content_type}
|
184
|
+
self.file = {:tempfile => new_path, :filename => original_filename, :content_type => @content_type}
|
191
185
|
else
|
192
|
-
self.file = {:tempfile => new_path, :content_type => content_type}
|
186
|
+
self.file = {:tempfile => new_path, :content_type => @content_type}
|
193
187
|
end
|
194
188
|
self
|
195
189
|
end
|
@@ -265,12 +259,10 @@ module CarrierWave
|
|
265
259
|
# [String] the content type of the file
|
266
260
|
#
|
267
261
|
def content_type
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
@content_type = ::MIME::Types.type_for(path).first.to_s
|
273
|
-
end
|
262
|
+
@content_type ||=
|
263
|
+
existing_content_type ||
|
264
|
+
marcel_magic_content_type ||
|
265
|
+
mini_mime_content_type
|
274
266
|
end
|
275
267
|
|
276
268
|
##
|
@@ -313,7 +305,7 @@ module CarrierWave
|
|
313
305
|
def mkdir!(path, directory_permissions)
|
314
306
|
options = {}
|
315
307
|
options[:mode] = directory_permissions if directory_permissions
|
316
|
-
FileUtils.mkdir_p(File.dirname(path), options) unless File.exist?(File.dirname(path))
|
308
|
+
FileUtils.mkdir_p(File.dirname(path), **options) unless File.exist?(File.dirname(path))
|
317
309
|
end
|
318
310
|
|
319
311
|
def chmod!(path, permissions)
|
@@ -326,10 +318,39 @@ module CarrierWave
|
|
326
318
|
name = File.basename(name)
|
327
319
|
name = name.gsub(sanitize_regexp,"_")
|
328
320
|
name = "_#{name}" if name =~ /\A\.+\z/
|
329
|
-
name = "unnamed" if name.size
|
321
|
+
name = "unnamed" if name.size.zero?
|
330
322
|
return name.mb_chars.to_s
|
331
323
|
end
|
332
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 marcel_magic_content_type
|
332
|
+
if path
|
333
|
+
type = File.open(path) do |file|
|
334
|
+
Marcel::Magic.by_magic(file).try(:type)
|
335
|
+
end
|
336
|
+
|
337
|
+
if type.nil?
|
338
|
+
type = Marcel::Magic.by_path(path).try(:type)
|
339
|
+
type = 'invalid/invalid' unless type.nil? || type.start_with?('text/')
|
340
|
+
end
|
341
|
+
|
342
|
+
type
|
343
|
+
end
|
344
|
+
rescue Errno::ENOENT
|
345
|
+
nil
|
346
|
+
end
|
347
|
+
|
348
|
+
def mini_mime_content_type
|
349
|
+
return unless path
|
350
|
+
mime_type = ::MiniMime.lookup_by_filename(path)
|
351
|
+
@content_type = (mime_type && mime_type.content_type).to_s
|
352
|
+
end
|
353
|
+
|
333
354
|
def split_extension(filename)
|
334
355
|
# regular expressions to try for identifying extensions
|
335
356
|
extension_matchers = [
|
@@ -110,8 +110,8 @@ module CarrierWave
|
|
110
110
|
|
111
111
|
def clean_cache!(seconds)
|
112
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
|
113
|
+
# generate_cache_id returns key formated TIMEINT-PID(-COUNTER)-RND
|
114
|
+
time = dir.scan(/(\d+)-\d+-\d+(?:-\d+)?/).first.map(&:to_i)
|
115
115
|
time = Time.at(*time)
|
116
116
|
if time < (Time.now.utc - seconds)
|
117
117
|
FileUtils.rm_rf(dir)
|
@@ -30,7 +30,7 @@ module CarrierWave
|
|
30
30
|
#
|
31
31
|
# Google credentials contain the following keys:
|
32
32
|
# [:google_storage_access_key_id]
|
33
|
-
# [:
|
33
|
+
# [:google_storage_secret_access_key]
|
34
34
|
#
|
35
35
|
#
|
36
36
|
# Local 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
|
##
|
@@ -138,9 +146,10 @@ module CarrierWave
|
|
138
146
|
:key => uploader.fog_directory,
|
139
147
|
:public => uploader.fog_public
|
140
148
|
).files.all(:prefix => uploader.cache_dir).each do |file|
|
141
|
-
# generate_cache_id returns key formated TIMEINT-PID-COUNTER-RND
|
142
|
-
|
143
|
-
|
149
|
+
# generate_cache_id returns key formated TIMEINT-PID(-COUNTER)-RND
|
150
|
+
matched = file.key.match(/(\d+)-\d+-\d+(?:-\d+)?/)
|
151
|
+
next unless matched
|
152
|
+
time = Time.at(matched[1].to_i)
|
144
153
|
file.destroy if time < (Time.now.utc - seconds)
|
145
154
|
end
|
146
155
|
end
|
@@ -153,6 +162,8 @@ module CarrierWave
|
|
153
162
|
end
|
154
163
|
|
155
164
|
class File
|
165
|
+
DEFAULT_S3_REGION = 'us-east-1'
|
166
|
+
|
156
167
|
include CarrierWave::Utilities::Uri
|
157
168
|
|
158
169
|
##
|
@@ -177,7 +188,7 @@ module CarrierWave
|
|
177
188
|
|
178
189
|
##
|
179
190
|
# Return a temporary authenticated url to a private file, if available
|
180
|
-
# Only supported for AWS, Rackspace and
|
191
|
+
# Only supported for AWS, Rackspace, Google, AzureRM and Aliyun providers
|
181
192
|
#
|
182
193
|
# === Returns
|
183
194
|
#
|
@@ -186,11 +197,11 @@ module CarrierWave
|
|
186
197
|
# [NilClass] no authenticated url available
|
187
198
|
#
|
188
199
|
def authenticated_url(options = {})
|
189
|
-
if ['AWS', 'Google', 'Rackspace', 'OpenStack'].include?(@uploader.fog_credentials[:provider])
|
200
|
+
if ['AWS', 'Google', 'Rackspace', 'OpenStack', 'AzureRM', 'Aliyun', 'backblaze'].include?(@uploader.fog_credentials[:provider])
|
190
201
|
# avoid a get by using local references
|
191
202
|
local_directory = connection.directories.new(:key => @uploader.fog_directory)
|
192
203
|
local_file = local_directory.files.new(:key => path)
|
193
|
-
expire_at = ::Fog::Time.now
|
204
|
+
expire_at = options[:expire_at] || ::Fog::Time.now.since(@uploader.fog_authenticated_url_expiration.to_i)
|
194
205
|
case @uploader.fog_credentials[:provider]
|
195
206
|
when 'AWS', 'Google'
|
196
207
|
# Older versions of fog-google do not support options as a parameter
|
@@ -200,10 +211,11 @@ module CarrierWave
|
|
200
211
|
warn "Options hash not supported in #{local_file.class}. You may need to upgrade your Fog provider."
|
201
212
|
local_file.url(expire_at)
|
202
213
|
end
|
203
|
-
when 'Rackspace'
|
214
|
+
when 'Rackspace', 'OpenStack'
|
204
215
|
connection.get_object_https_url(@uploader.fog_directory, path, expire_at, options)
|
205
|
-
when '
|
206
|
-
|
216
|
+
when 'Aliyun'
|
217
|
+
expire_at = expire_at - Time.now
|
218
|
+
local_file.url(expire_at)
|
207
219
|
else
|
208
220
|
local_file.url(expire_at)
|
209
221
|
end
|
@@ -218,7 +230,7 @@ module CarrierWave
|
|
218
230
|
# [String] value of content-type
|
219
231
|
#
|
220
232
|
def content_type
|
221
|
-
@content_type ||
|
233
|
+
@content_type || file.try(:content_type)
|
222
234
|
end
|
223
235
|
|
224
236
|
##
|
@@ -241,7 +253,9 @@ module CarrierWave
|
|
241
253
|
#
|
242
254
|
def delete
|
243
255
|
# avoid a get by just using local reference
|
244
|
-
directory.files.new(:key => path).destroy
|
256
|
+
directory.files.new(:key => path).destroy.tap do |result|
|
257
|
+
@file = nil if result
|
258
|
+
end
|
245
259
|
end
|
246
260
|
|
247
261
|
##
|
@@ -373,9 +387,15 @@ module CarrierWave
|
|
373
387
|
if valid_subdomain
|
374
388
|
s3_subdomain = @uploader.fog_aws_accelerate ? "s3-accelerate" : "s3"
|
375
389
|
"#{protocol}://#{@uploader.fog_directory}.#{s3_subdomain}.amazonaws.com/#{encoded_path}"
|
376
|
-
else
|
377
|
-
|
378
|
-
|
390
|
+
else # directory is not a valid subdomain, so use path style for access
|
391
|
+
region = @uploader.fog_credentials[:region].to_s
|
392
|
+
host = case region
|
393
|
+
when DEFAULT_S3_REGION, ''
|
394
|
+
's3.amazonaws.com'
|
395
|
+
else
|
396
|
+
"s3.#{region}.amazonaws.com"
|
397
|
+
end
|
398
|
+
"#{protocol}://#{host}/#{@uploader.fog_directory}/#{encoded_path}"
|
379
399
|
end
|
380
400
|
end
|
381
401
|
when 'Google'
|
@@ -431,7 +451,7 @@ module CarrierWave
|
|
431
451
|
# @return [CarrierWave::Storage::Fog::File] the location where the file will be stored.
|
432
452
|
#
|
433
453
|
def copy_to(new_path)
|
434
|
-
connection.copy_object(@uploader.fog_directory, file.key, @uploader.fog_directory, new_path,
|
454
|
+
connection.copy_object(@uploader.fog_directory, file.key, @uploader.fog_directory, new_path, copy_options)
|
435
455
|
CarrierWave::Storage::Fog::File.new(@uploader, @base, new_path)
|
436
456
|
end
|
437
457
|
|
@@ -475,9 +495,18 @@ module CarrierWave
|
|
475
495
|
@file ||= directory.files.head(path)
|
476
496
|
end
|
477
497
|
|
498
|
+
def copy_options
|
499
|
+
options = {}
|
500
|
+
options.merge!(acl_header) if acl_header.present?
|
501
|
+
options['Content-Type'] ||= content_type if content_type
|
502
|
+
options.merge(@uploader.fog_attributes)
|
503
|
+
end
|
504
|
+
|
478
505
|
def acl_header
|
479
506
|
if fog_provider == 'AWS'
|
480
507
|
{ 'x-amz-acl' => @uploader.fog_public ? 'public-read' : 'private' }
|
508
|
+
elsif fog_provider == "Google"
|
509
|
+
@uploader.fog_public ? { destination_predefined_acl: "publicRead" } : {}
|
481
510
|
else
|
482
511
|
{}
|
483
512
|
end
|
@@ -499,7 +528,7 @@ module CarrierWave
|
|
499
528
|
end
|
500
529
|
|
501
530
|
def url_options_supported?(local_file)
|
502
|
-
parameters =
|
531
|
+
parameters = local_file.method(:url).parameters
|
503
532
|
parameters.count == 2 && parameters[1].include?(:options)
|
504
533
|
end
|
505
534
|
end
|
data/lib/carrierwave/storage.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
1
3
|
module CarrierWave
|
2
4
|
|
3
5
|
class FormNotMultipart < UploadError
|
@@ -23,9 +25,9 @@ module CarrierWave
|
|
23
25
|
#
|
24
26
|
def self.generate_cache_id
|
25
27
|
[Time.now.utc.to_i,
|
26
|
-
|
27
|
-
'%04d' % (CarrierWave::CacheCounter.increment %
|
28
|
-
'%04d' %
|
28
|
+
SecureRandom.random_number(1_000_000_000_000_000),
|
29
|
+
'%04d' % (CarrierWave::CacheCounter.increment % 10_000),
|
30
|
+
'%04d' % SecureRandom.random_number(10_000)
|
29
31
|
].map(&:to_s).join('-')
|
30
32
|
end
|
31
33
|
|
@@ -36,6 +38,16 @@ module CarrierWave
|
|
36
38
|
include CarrierWave::Uploader::Callbacks
|
37
39
|
include CarrierWave::Uploader::Configuration
|
38
40
|
|
41
|
+
included do
|
42
|
+
prepend Module.new {
|
43
|
+
def initialize(*)
|
44
|
+
super
|
45
|
+
@staged = false
|
46
|
+
end
|
47
|
+
}
|
48
|
+
attr_accessor :staged
|
49
|
+
end
|
50
|
+
|
39
51
|
module ClassMethods
|
40
52
|
|
41
53
|
##
|
@@ -52,7 +64,7 @@ module CarrierWave
|
|
52
64
|
# It's recommended that you keep cache files in one place only.
|
53
65
|
#
|
54
66
|
def clean_cached_files!(seconds=60*60*24)
|
55
|
-
cache_storage.new(CarrierWave::Uploader::Base.new).clean_cache!(seconds)
|
67
|
+
(cache_storage || storage).new(CarrierWave::Uploader::Base.new).clean_cache!(seconds)
|
56
68
|
end
|
57
69
|
end
|
58
70
|
|
@@ -64,7 +76,7 @@ module CarrierWave
|
|
64
76
|
# [Bool] whether the current file is cached
|
65
77
|
#
|
66
78
|
def cached?
|
67
|
-
|
79
|
+
!!@cache_id
|
68
80
|
end
|
69
81
|
|
70
82
|
##
|
@@ -78,14 +90,8 @@ module CarrierWave
|
|
78
90
|
end
|
79
91
|
|
80
92
|
def sanitized_file
|
81
|
-
|
82
|
-
|
83
|
-
sanitized = CarrierWave::Storage::Fog.new(self).retrieve!(File.basename(_content.path))
|
84
|
-
else
|
85
|
-
sanitized = SanitizedFile.new :tempfile => StringIO.new(_content),
|
86
|
-
:filename => File.basename(path), :content_type => file.content_type
|
87
|
-
end
|
88
|
-
sanitized
|
93
|
+
ActiveSupport::Deprecation.warn('#sanitized_file is deprecated, use #file instead.')
|
94
|
+
file
|
89
95
|
end
|
90
96
|
|
91
97
|
##
|
@@ -96,7 +102,7 @@ module CarrierWave
|
|
96
102
|
# [String] a cache name, in the format TIMEINT-PID-COUNTER-RND/filename.txt
|
97
103
|
#
|
98
104
|
def cache_name
|
99
|
-
File.join(cache_id, full_original_filename) if cache_id
|
105
|
+
File.join(cache_id, full_original_filename) if cache_id && original_filename
|
100
106
|
end
|
101
107
|
|
102
108
|
##
|
@@ -115,7 +121,7 @@ module CarrierWave
|
|
115
121
|
#
|
116
122
|
# [CarrierWave::FormNotMultipart] if the assigned parameter is a string
|
117
123
|
#
|
118
|
-
def cache!(new_file =
|
124
|
+
def cache!(new_file = file)
|
119
125
|
new_file = CarrierWave::SanitizedFile.new(new_file)
|
120
126
|
return if new_file.empty?
|
121
127
|
|
@@ -123,6 +129,7 @@ module CarrierWave
|
|
123
129
|
|
124
130
|
self.cache_id = CarrierWave.generate_cache_id unless cache_id
|
125
131
|
|
132
|
+
@staged = true
|
126
133
|
@filename = new_file.filename
|
127
134
|
self.original_filename = new_file.filename
|
128
135
|
|
@@ -156,6 +163,7 @@ module CarrierWave
|
|
156
163
|
def retrieve_from_cache!(cache_name)
|
157
164
|
with_callbacks(:retrieve_from_cache, cache_name) do
|
158
165
|
self.cache_id, self.original_filename = cache_name.to_s.split('/', 2)
|
166
|
+
@staged = true
|
159
167
|
@filename = original_filename
|
160
168
|
@file = cache_storage.retrieve_from_cache!(full_filename(original_filename))
|
161
169
|
end
|
@@ -200,7 +208,7 @@ module CarrierWave
|
|
200
208
|
end
|
201
209
|
|
202
210
|
def cache_storage
|
203
|
-
@cache_storage ||= self.class.cache_storage.new(self)
|
211
|
+
@cache_storage ||= (self.class.cache_storage || self.class.storage).new(self)
|
204
212
|
end
|
205
213
|
end # Cache
|
206
214
|
end # Uploader
|