salebot_uploader 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|