salebot_uploader 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +0 -0
- data/lib/generators/templates/uploader.rb.erb +9 -0
- data/lib/generators/uploader_generator.rb +7 -0
- data/lib/salebot_uploader/compatibility/paperclip.rb +104 -0
- data/lib/salebot_uploader/downloader/base.rb +101 -0
- data/lib/salebot_uploader/downloader/remote_file.rb +68 -0
- data/lib/salebot_uploader/error.rb +8 -0
- data/lib/salebot_uploader/locale/en.yml +17 -0
- data/lib/salebot_uploader/mount.rb +446 -0
- data/lib/salebot_uploader/mounter.rb +255 -0
- data/lib/salebot_uploader/orm/activerecord.rb +68 -0
- data/lib/salebot_uploader/processing/mini_magick.rb +194 -0
- data/lib/salebot_uploader/processing/rmagick.rb +402 -0
- data/lib/salebot_uploader/processing/vips.rb +284 -0
- data/lib/salebot_uploader/processing.rb +3 -0
- data/lib/salebot_uploader/sanitized_file.rb +357 -0
- data/lib/salebot_uploader/storage/abstract.rb +41 -0
- data/lib/salebot_uploader/storage/file.rb +124 -0
- data/lib/salebot_uploader/storage/fog.rb +547 -0
- data/lib/salebot_uploader/storage.rb +3 -0
- data/lib/salebot_uploader/test/matchers.rb +398 -0
- data/lib/salebot_uploader/uploader/cache.rb +223 -0
- data/lib/salebot_uploader/uploader/callbacks.rb +33 -0
- data/lib/salebot_uploader/uploader/configuration.rb +184 -0
- data/lib/salebot_uploader/uploader/content_type_allowlist.rb +61 -0
- data/lib/salebot_uploader/uploader/content_type_denylist.rb +62 -0
- data/lib/salebot_uploader/uploader/default_url.rb +17 -0
- data/lib/salebot_uploader/uploader/dimension.rb +66 -0
- data/lib/salebot_uploader/uploader/download.rb +24 -0
- data/lib/salebot_uploader/uploader/extension_allowlist.rb +63 -0
- data/lib/salebot_uploader/uploader/extension_denylist.rb +64 -0
- data/lib/salebot_uploader/uploader/file_size.rb +43 -0
- data/lib/salebot_uploader/uploader/mountable.rb +44 -0
- data/lib/salebot_uploader/uploader/processing.rb +125 -0
- data/lib/salebot_uploader/uploader/proxy.rb +99 -0
- data/lib/salebot_uploader/uploader/remove.rb +21 -0
- data/lib/salebot_uploader/uploader/serialization.rb +28 -0
- data/lib/salebot_uploader/uploader/store.rb +142 -0
- data/lib/salebot_uploader/uploader/url.rb +44 -0
- data/lib/salebot_uploader/uploader/versions.rb +350 -0
- data/lib/salebot_uploader/uploader.rb +53 -0
- data/lib/salebot_uploader/utilities/file_name.rb +47 -0
- data/lib/salebot_uploader/utilities/uri.rb +26 -0
- data/lib/salebot_uploader/utilities.rb +7 -0
- data/lib/salebot_uploader/validations/active_model.rb +76 -0
- data/lib/salebot_uploader/version.rb +3 -0
- data/lib/salebot_uploader.rb +62 -0
- metadata +392 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Storage
|
3
|
+
|
4
|
+
##
|
5
|
+
# File storage stores file to the Filesystem (surprising, no?). There's really not much
|
6
|
+
# to it, it uses the store_dir defined on the uploader as the storage location. That's
|
7
|
+
# pretty much it.
|
8
|
+
#
|
9
|
+
class File < Abstract
|
10
|
+
def initialize(*)
|
11
|
+
super
|
12
|
+
@cache_called = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Move the file to the uploader's store path.
|
17
|
+
#
|
18
|
+
# By default, store!() uses copy_to(), which operates by copying the file
|
19
|
+
# from the cache to the store, then deleting the file from the cache.
|
20
|
+
# If move_to_store() is overridden to return true, then store!() uses move_to(),
|
21
|
+
# which simply moves the file from cache to store. Useful for large files.
|
22
|
+
#
|
23
|
+
# === Parameters
|
24
|
+
#
|
25
|
+
# [file (SalebotUploader::SanitizedFile)] the file to store
|
26
|
+
#
|
27
|
+
# === Returns
|
28
|
+
#
|
29
|
+
# [SalebotUploader::SanitizedFile] a sanitized file
|
30
|
+
#
|
31
|
+
def store!(file)
|
32
|
+
path = ::File.expand_path(uploader.store_path, uploader.root)
|
33
|
+
if uploader.move_to_store
|
34
|
+
file.move_to(path, uploader.permissions, uploader.directory_permissions)
|
35
|
+
else
|
36
|
+
file.copy_to(path, uploader.permissions, uploader.directory_permissions)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Retrieve the file from its store path
|
42
|
+
#
|
43
|
+
# === Parameters
|
44
|
+
#
|
45
|
+
# [identifier (String)] the filename of the file
|
46
|
+
#
|
47
|
+
# === Returns
|
48
|
+
#
|
49
|
+
# [SalebotUploader::SanitizedFile] a sanitized file
|
50
|
+
#
|
51
|
+
def retrieve!(identifier)
|
52
|
+
path = ::File.expand_path(uploader.store_path(identifier), uploader.root)
|
53
|
+
SalebotUploader::SanitizedFile.new(path)
|
54
|
+
end
|
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
|
+
# [SalebotUploader::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
|
+
# [SalebotUploader::InvalidParameter] if the cache_name is incorrectly formatted.
|
89
|
+
#
|
90
|
+
def retrieve_from_cache!(identifier)
|
91
|
+
SalebotUploader::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, '*'), uploader.root)).each do |dir|
|
113
|
+
# generate_cache_id returns key formatted TIMEINT-PID(-COUNTER)-RND
|
114
|
+
matched = dir.scan(/(\d+)-\d+-\d+(?:-\d+)?/).first
|
115
|
+
next unless matched
|
116
|
+
time = Time.at(matched[0].to_i)
|
117
|
+
if time < (Time.now.utc - seconds)
|
118
|
+
FileUtils.rm_rf(dir)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end # File
|
123
|
+
end # Storage
|
124
|
+
end # SalebotUploader
|
@@ -0,0 +1,547 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Storage
|
3
|
+
|
4
|
+
##
|
5
|
+
# Stores things using the "fog" gem.
|
6
|
+
#
|
7
|
+
# fog supports storing files with AWS, Google, Local and Rackspace
|
8
|
+
#
|
9
|
+
# You need to setup some options to configure your usage:
|
10
|
+
#
|
11
|
+
# [:fog_credentials] host info and credentials for service
|
12
|
+
# [:fog_directory] specifies name of directory to store data in, assumed to already exist
|
13
|
+
#
|
14
|
+
# [:fog_attributes] (optional) additional attributes to set on files
|
15
|
+
# [:fog_public] (optional) public readability, defaults to true
|
16
|
+
# [:fog_authenticated_url_expiration] (optional) time (in seconds) that authenticated urls
|
17
|
+
# will be valid, when fog_public is false and provider is AWS or Google, defaults to 600
|
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
|
+
#
|
22
|
+
#
|
23
|
+
# AWS credentials contain the following keys:
|
24
|
+
#
|
25
|
+
# [:aws_access_key_id]
|
26
|
+
# [:aws_secret_access_key]
|
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', 'eu-central-1']
|
29
|
+
#
|
30
|
+
#
|
31
|
+
# Google credentials contain the following keys:
|
32
|
+
# [:google_storage_access_key_id]
|
33
|
+
# [:google_storage_secret_access_key]
|
34
|
+
#
|
35
|
+
#
|
36
|
+
# Local credentials contain the following keys:
|
37
|
+
#
|
38
|
+
# [:local_root] local path to files
|
39
|
+
#
|
40
|
+
#
|
41
|
+
# Rackspace credentials contain the following keys:
|
42
|
+
#
|
43
|
+
# [:rackspace_username]
|
44
|
+
# [:rackspace_api_key]
|
45
|
+
#
|
46
|
+
#
|
47
|
+
# A full example with AWS credentials:
|
48
|
+
# SalebotUploader.configure do |config|
|
49
|
+
# config.fog_credentials = {
|
50
|
+
# :aws_access_key_id => 'xxxxxx',
|
51
|
+
# :aws_secret_access_key => 'yyyyyy',
|
52
|
+
# :provider => 'AWS'
|
53
|
+
# }
|
54
|
+
# config.fog_directory = 'directoryname'
|
55
|
+
# config.fog_public = true
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
class Fog < Abstract
|
59
|
+
class << self
|
60
|
+
def connection_cache
|
61
|
+
@connection_cache ||= {}
|
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 = SalebotUploader::Uploader::Base.fog_credentials
|
67
|
+
if fog_credentials.present?
|
68
|
+
SalebotUploader::Storage::Fog.connection_cache[fog_credentials] ||= ::Fog::Storage.new(fog_credentials)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Store a file
|
75
|
+
#
|
76
|
+
# === Parameters
|
77
|
+
#
|
78
|
+
# [file (SalebotUploader::SanitizedFile)] the file to store
|
79
|
+
#
|
80
|
+
# === Returns
|
81
|
+
#
|
82
|
+
# [SalebotUploader::Storage::Fog::File] the stored file
|
83
|
+
#
|
84
|
+
def store!(file)
|
85
|
+
f = SalebotUploader::Storage::Fog::File.new(uploader, self, uploader.store_path)
|
86
|
+
f.store(file)
|
87
|
+
f
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Retrieve a file
|
92
|
+
#
|
93
|
+
# === Parameters
|
94
|
+
#
|
95
|
+
# [identifier (String)] unique identifier for file
|
96
|
+
#
|
97
|
+
# === Returns
|
98
|
+
#
|
99
|
+
# [SalebotUploader::Storage::Fog::File] the stored file
|
100
|
+
#
|
101
|
+
def retrieve!(identifier)
|
102
|
+
SalebotUploader::Storage::Fog::File.new(uploader, self, uploader.store_path(identifier))
|
103
|
+
end
|
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
|
+
# [SalebotUploader::SanitizedFile] a sanitized file
|
115
|
+
#
|
116
|
+
def cache!(new_file)
|
117
|
+
f = SalebotUploader::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
|
+
# [SalebotUploader::InvalidParameter] if the cache_name is incorrectly formatted.
|
132
|
+
#
|
133
|
+
def retrieve_from_cache!(identifier)
|
134
|
+
SalebotUploader::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 formatted 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)
|
153
|
+
file.destroy if time < (Time.now.utc - seconds)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def connection
|
158
|
+
@connection ||= begin
|
159
|
+
options = credentials = uploader.fog_credentials
|
160
|
+
self.class.connection_cache[credentials] ||= ::Fog::Storage.new(options)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class File
|
165
|
+
DEFAULT_S3_REGION = 'us-east-1'
|
166
|
+
|
167
|
+
include SalebotUploader::Utilities::Uri
|
168
|
+
include SalebotUploader::Utilities::FileName
|
169
|
+
|
170
|
+
##
|
171
|
+
# Current local path to file
|
172
|
+
#
|
173
|
+
# === Returns
|
174
|
+
#
|
175
|
+
# [String] a path to file
|
176
|
+
#
|
177
|
+
attr_reader :path
|
178
|
+
|
179
|
+
##
|
180
|
+
# Return all attributes from file
|
181
|
+
#
|
182
|
+
# === Returns
|
183
|
+
#
|
184
|
+
# [Hash] attributes from file
|
185
|
+
#
|
186
|
+
def attributes
|
187
|
+
file.attributes
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# Return a temporary authenticated url to a private file, if available
|
192
|
+
# Only supported for AWS, Rackspace, Google, AzureRM and Aliyun providers
|
193
|
+
#
|
194
|
+
# === Returns
|
195
|
+
#
|
196
|
+
# [String] temporary authenticated url
|
197
|
+
# or
|
198
|
+
# [NilClass] no authenticated url available
|
199
|
+
#
|
200
|
+
def authenticated_url(options = {})
|
201
|
+
if ['AWS', 'Google', 'Rackspace', 'OpenStack', 'AzureRM', 'Aliyun', 'backblaze'].include?(fog_provider)
|
202
|
+
# avoid a get by using local references
|
203
|
+
local_directory = connection.directories.new(:key => @uploader.fog_directory)
|
204
|
+
local_file = local_directory.files.new(:key => path)
|
205
|
+
expire_at = options[:expire_at] || ::Fog::Time.now.since(@uploader.fog_authenticated_url_expiration.to_i)
|
206
|
+
case fog_provider
|
207
|
+
when 'AWS', 'Google'
|
208
|
+
# Older versions of fog-google do not support options as a parameter
|
209
|
+
if url_options_supported?(local_file)
|
210
|
+
local_file.url(expire_at, options)
|
211
|
+
else
|
212
|
+
warn "Options hash not supported in #{local_file.class}. You may need to upgrade your Fog provider."
|
213
|
+
local_file.url(expire_at)
|
214
|
+
end
|
215
|
+
when 'Rackspace', 'OpenStack'
|
216
|
+
connection.get_object_https_url(@uploader.fog_directory, path, expire_at, options)
|
217
|
+
when 'Aliyun'
|
218
|
+
expire_at -= Time.now
|
219
|
+
local_file.url(expire_at)
|
220
|
+
else
|
221
|
+
local_file.url(expire_at)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
##
|
227
|
+
# Lookup value for file content-type header
|
228
|
+
#
|
229
|
+
# === Returns
|
230
|
+
#
|
231
|
+
# [String] value of content-type
|
232
|
+
#
|
233
|
+
def content_type
|
234
|
+
@content_type || file.try(:content_type)
|
235
|
+
end
|
236
|
+
|
237
|
+
##
|
238
|
+
# Set non-default content-type header (default is file.content_type)
|
239
|
+
#
|
240
|
+
# === Returns
|
241
|
+
#
|
242
|
+
# [String] returns new content type value
|
243
|
+
#
|
244
|
+
def content_type=(new_content_type)
|
245
|
+
@content_type = new_content_type
|
246
|
+
end
|
247
|
+
|
248
|
+
##
|
249
|
+
# Remove the file from service
|
250
|
+
#
|
251
|
+
# === Returns
|
252
|
+
#
|
253
|
+
# [Boolean] true for success or raises error
|
254
|
+
#
|
255
|
+
def delete
|
256
|
+
# avoid a get by just using local reference
|
257
|
+
directory.files.new(:key => path).destroy.tap do |result|
|
258
|
+
@file = nil if result
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
##
|
263
|
+
# deprecated: All attributes from file (includes headers)
|
264
|
+
#
|
265
|
+
# === Returns
|
266
|
+
#
|
267
|
+
# [Hash] attributes from file
|
268
|
+
#
|
269
|
+
def headers
|
270
|
+
location = caller.first
|
271
|
+
warning = "[yellow][WARN] headers is deprecated, use attributes instead[/]"
|
272
|
+
warning << " [light_black](#{location})[/]"
|
273
|
+
Formatador.display_line(warning)
|
274
|
+
attributes
|
275
|
+
end
|
276
|
+
|
277
|
+
def initialize(uploader, base, path)
|
278
|
+
@uploader, @base, @path, @content_type = uploader, base, path, nil
|
279
|
+
end
|
280
|
+
|
281
|
+
##
|
282
|
+
# Read content of file from service
|
283
|
+
#
|
284
|
+
# === Returns
|
285
|
+
#
|
286
|
+
# [String] contents of file
|
287
|
+
def read
|
288
|
+
file_body = file&.body
|
289
|
+
|
290
|
+
return if file_body.nil?
|
291
|
+
return file_body unless file_body.is_a?(::File)
|
292
|
+
|
293
|
+
# Fog::Storage::XXX::File#body could return the source file which was uploaded to the remote server.
|
294
|
+
return read_source_file if ::File.exist?(file_body.path)
|
295
|
+
|
296
|
+
# If the source file doesn't exist, the remote content is read
|
297
|
+
@file = nil
|
298
|
+
file.body
|
299
|
+
end
|
300
|
+
|
301
|
+
##
|
302
|
+
# Return size of file body
|
303
|
+
#
|
304
|
+
# === Returns
|
305
|
+
#
|
306
|
+
# [Integer] size of file body
|
307
|
+
#
|
308
|
+
def size
|
309
|
+
file.nil? ? 0 : file.content_length
|
310
|
+
end
|
311
|
+
|
312
|
+
##
|
313
|
+
# Check if the file exists on the remote service
|
314
|
+
#
|
315
|
+
# === Returns
|
316
|
+
#
|
317
|
+
# [Boolean] true if file exists or false
|
318
|
+
def exists?
|
319
|
+
!!file
|
320
|
+
end
|
321
|
+
|
322
|
+
##
|
323
|
+
# Write file to service
|
324
|
+
#
|
325
|
+
# === Returns
|
326
|
+
#
|
327
|
+
# [Boolean] true on success or raises error
|
328
|
+
def store(new_file)
|
329
|
+
if new_file.is_a?(self.class)
|
330
|
+
new_file.copy_to(path)
|
331
|
+
else
|
332
|
+
fog_file = new_file.to_file
|
333
|
+
@content_type ||= new_file.content_type
|
334
|
+
@file = directory.files.create({
|
335
|
+
:body => fog_file || new_file.read,
|
336
|
+
:content_type => @content_type,
|
337
|
+
:key => path,
|
338
|
+
:public => @uploader.fog_public
|
339
|
+
}.merge(@uploader.fog_attributes))
|
340
|
+
fog_file.close if fog_file && !fog_file.closed?
|
341
|
+
end
|
342
|
+
true
|
343
|
+
end
|
344
|
+
|
345
|
+
##
|
346
|
+
# Return a url to a public file, if available
|
347
|
+
#
|
348
|
+
# === Returns
|
349
|
+
#
|
350
|
+
# [String] public url
|
351
|
+
# or
|
352
|
+
# [NilClass] no public url available
|
353
|
+
#
|
354
|
+
def public_url
|
355
|
+
encoded_path = encode_path(path)
|
356
|
+
if (host = @uploader.asset_host)
|
357
|
+
if host.respond_to? :call
|
358
|
+
"#{host.call(self)}/#{encoded_path}"
|
359
|
+
else
|
360
|
+
"#{host}/#{encoded_path}"
|
361
|
+
end
|
362
|
+
else
|
363
|
+
# AWS/Google optimized for speed over correctness
|
364
|
+
case fog_provider
|
365
|
+
when 'AWS'
|
366
|
+
# check if some endpoint is set in fog_credentials
|
367
|
+
if @uploader.fog_credentials.has_key?(:endpoint)
|
368
|
+
"#{@uploader.fog_credentials[:endpoint]}/#{@uploader.fog_directory}/#{encoded_path}"
|
369
|
+
else
|
370
|
+
protocol = @uploader.fog_use_ssl_for_aws ? "https" : "http"
|
371
|
+
|
372
|
+
subdomain_regex = /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9\.]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
|
373
|
+
# To use the virtual-hosted style, the bucket name needs to be representable as a subdomain
|
374
|
+
use_virtual_hosted_style = @uploader.fog_directory.to_s =~ subdomain_regex && !(protocol == 'https' && @uploader.fog_directory =~ /\./)
|
375
|
+
|
376
|
+
region = @uploader.fog_credentials[:region].to_s
|
377
|
+
regional_host = case region
|
378
|
+
when DEFAULT_S3_REGION, ''
|
379
|
+
's3.amazonaws.com'
|
380
|
+
else
|
381
|
+
"s3.#{region}.amazonaws.com"
|
382
|
+
end
|
383
|
+
|
384
|
+
if use_virtual_hosted_style
|
385
|
+
regional_host = 's3-accelerate.amazonaws.com' if @uploader.fog_aws_accelerate
|
386
|
+
"#{protocol}://#{@uploader.fog_directory}.#{regional_host}/#{encoded_path}"
|
387
|
+
else # directory is not a valid subdomain, so use path style for access
|
388
|
+
"#{protocol}://#{regional_host}/#{@uploader.fog_directory}/#{encoded_path}"
|
389
|
+
end
|
390
|
+
end
|
391
|
+
when 'Google'
|
392
|
+
# https://cloud.google.com/storage/docs/access-public-data
|
393
|
+
"https://storage.googleapis.com/#{@uploader.fog_directory}/#{encoded_path}"
|
394
|
+
else
|
395
|
+
# avoid a get by just using local reference
|
396
|
+
directory.files.new(:key => path).public_url
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
##
|
402
|
+
# Return url to file, if available
|
403
|
+
#
|
404
|
+
# === Returns
|
405
|
+
#
|
406
|
+
# [String] url
|
407
|
+
# or
|
408
|
+
# [NilClass] no url available
|
409
|
+
#
|
410
|
+
def url(options = {})
|
411
|
+
if !@uploader.fog_public
|
412
|
+
authenticated_url(options)
|
413
|
+
else
|
414
|
+
public_url
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
##
|
419
|
+
# Return file name, if available
|
420
|
+
#
|
421
|
+
# === Returns
|
422
|
+
#
|
423
|
+
# [String] file name
|
424
|
+
# or
|
425
|
+
# [NilClass] no file name available
|
426
|
+
#
|
427
|
+
def filename(options = {})
|
428
|
+
return unless (file_url = url(options))
|
429
|
+
CGI.unescape(file_url.split('?').first).gsub(/.*\/(.*?$)/, '\1')
|
430
|
+
end
|
431
|
+
|
432
|
+
##
|
433
|
+
# Creates a copy of this file and returns it.
|
434
|
+
#
|
435
|
+
# === Parameters
|
436
|
+
#
|
437
|
+
# [new_path (String)] The path where the file should be copied to.
|
438
|
+
#
|
439
|
+
# === Returns
|
440
|
+
#
|
441
|
+
# @return [SalebotUploader::Storage::Fog::File] the location where the file will be stored.
|
442
|
+
#
|
443
|
+
def copy_to(new_path)
|
444
|
+
file.copy(@uploader.fog_directory, new_path, copy_options)
|
445
|
+
SalebotUploader::Storage::Fog::File.new(@uploader, @base, new_path)
|
446
|
+
end
|
447
|
+
|
448
|
+
##
|
449
|
+
# Return the local file
|
450
|
+
#
|
451
|
+
# === Returns
|
452
|
+
#
|
453
|
+
# [File] The local file as Ruby's File class
|
454
|
+
# or
|
455
|
+
# [NilClass] When there's no file, or the file is remotely stored
|
456
|
+
#
|
457
|
+
def to_file
|
458
|
+
return nil unless file.body.is_a? ::File
|
459
|
+
|
460
|
+
if file.body.closed?
|
461
|
+
::File.open(file.body.path) # Reopen if it's already closed
|
462
|
+
else
|
463
|
+
file.body
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
private
|
468
|
+
|
469
|
+
##
|
470
|
+
# connection to service
|
471
|
+
#
|
472
|
+
# === Returns
|
473
|
+
#
|
474
|
+
# [Fog::#{provider}::Storage] connection to service
|
475
|
+
#
|
476
|
+
def connection
|
477
|
+
@base.connection
|
478
|
+
end
|
479
|
+
|
480
|
+
##
|
481
|
+
# local reference to directory containing file
|
482
|
+
#
|
483
|
+
# === Returns
|
484
|
+
#
|
485
|
+
# [Fog::#{provider}::Directory] containing directory
|
486
|
+
#
|
487
|
+
def directory
|
488
|
+
@directory ||= connection.directories.new(
|
489
|
+
:key => @uploader.fog_directory,
|
490
|
+
:public => @uploader.fog_public
|
491
|
+
)
|
492
|
+
end
|
493
|
+
|
494
|
+
##
|
495
|
+
# lookup file
|
496
|
+
#
|
497
|
+
# === Returns
|
498
|
+
#
|
499
|
+
# [Fog::#{provider}::File] file data from remote service
|
500
|
+
#
|
501
|
+
def file
|
502
|
+
@file ||= directory.files.head(path)
|
503
|
+
end
|
504
|
+
|
505
|
+
def copy_options
|
506
|
+
options = {}
|
507
|
+
options.merge!(acl_header) if acl_header.present?
|
508
|
+
options[fog_provider == "Google" ? :content_type : 'Content-Type'] ||= content_type if content_type
|
509
|
+
options.merge(@uploader.fog_attributes)
|
510
|
+
end
|
511
|
+
|
512
|
+
def acl_header
|
513
|
+
case fog_provider
|
514
|
+
when 'AWS'
|
515
|
+
{ 'x-amz-acl' => @uploader.fog_public ? 'public-read' : 'private' }
|
516
|
+
when "Google"
|
517
|
+
@uploader.fog_public ? { destination_predefined_acl: "publicRead" } : {}
|
518
|
+
else
|
519
|
+
{}
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
def fog_provider
|
524
|
+
@uploader.fog_credentials[:provider].to_s
|
525
|
+
end
|
526
|
+
|
527
|
+
def read_source_file
|
528
|
+
source_file = to_file
|
529
|
+
return unless source_file
|
530
|
+
|
531
|
+
begin
|
532
|
+
source_file.read
|
533
|
+
ensure
|
534
|
+
source_file.close
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
def url_options_supported?(local_file)
|
539
|
+
parameters = local_file.method(:url).parameters
|
540
|
+
parameters.count == 2 && parameters[1].include?(:options)
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
end # Fog
|
545
|
+
|
546
|
+
end # Storage
|
547
|
+
end # SalebotUploader
|