salebot_uploader 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +0 -0
  3. data/lib/generators/templates/uploader.rb.erb +9 -0
  4. data/lib/generators/uploader_generator.rb +7 -0
  5. data/lib/salebot_uploader/compatibility/paperclip.rb +104 -0
  6. data/lib/salebot_uploader/downloader/base.rb +101 -0
  7. data/lib/salebot_uploader/downloader/remote_file.rb +68 -0
  8. data/lib/salebot_uploader/error.rb +8 -0
  9. data/lib/salebot_uploader/locale/en.yml +17 -0
  10. data/lib/salebot_uploader/mount.rb +446 -0
  11. data/lib/salebot_uploader/mounter.rb +255 -0
  12. data/lib/salebot_uploader/orm/activerecord.rb +68 -0
  13. data/lib/salebot_uploader/processing/mini_magick.rb +194 -0
  14. data/lib/salebot_uploader/processing/rmagick.rb +402 -0
  15. data/lib/salebot_uploader/processing/vips.rb +284 -0
  16. data/lib/salebot_uploader/processing.rb +3 -0
  17. data/lib/salebot_uploader/sanitized_file.rb +357 -0
  18. data/lib/salebot_uploader/storage/abstract.rb +41 -0
  19. data/lib/salebot_uploader/storage/file.rb +124 -0
  20. data/lib/salebot_uploader/storage/fog.rb +547 -0
  21. data/lib/salebot_uploader/storage.rb +3 -0
  22. data/lib/salebot_uploader/test/matchers.rb +398 -0
  23. data/lib/salebot_uploader/uploader/cache.rb +223 -0
  24. data/lib/salebot_uploader/uploader/callbacks.rb +33 -0
  25. data/lib/salebot_uploader/uploader/configuration.rb +184 -0
  26. data/lib/salebot_uploader/uploader/content_type_allowlist.rb +61 -0
  27. data/lib/salebot_uploader/uploader/content_type_denylist.rb +62 -0
  28. data/lib/salebot_uploader/uploader/default_url.rb +17 -0
  29. data/lib/salebot_uploader/uploader/dimension.rb +66 -0
  30. data/lib/salebot_uploader/uploader/download.rb +24 -0
  31. data/lib/salebot_uploader/uploader/extension_allowlist.rb +63 -0
  32. data/lib/salebot_uploader/uploader/extension_denylist.rb +64 -0
  33. data/lib/salebot_uploader/uploader/file_size.rb +43 -0
  34. data/lib/salebot_uploader/uploader/mountable.rb +44 -0
  35. data/lib/salebot_uploader/uploader/processing.rb +125 -0
  36. data/lib/salebot_uploader/uploader/proxy.rb +99 -0
  37. data/lib/salebot_uploader/uploader/remove.rb +21 -0
  38. data/lib/salebot_uploader/uploader/serialization.rb +28 -0
  39. data/lib/salebot_uploader/uploader/store.rb +142 -0
  40. data/lib/salebot_uploader/uploader/url.rb +44 -0
  41. data/lib/salebot_uploader/uploader/versions.rb +350 -0
  42. data/lib/salebot_uploader/uploader.rb +53 -0
  43. data/lib/salebot_uploader/utilities/file_name.rb +47 -0
  44. data/lib/salebot_uploader/utilities/uri.rb +26 -0
  45. data/lib/salebot_uploader/utilities.rb +7 -0
  46. data/lib/salebot_uploader/validations/active_model.rb +76 -0
  47. data/lib/salebot_uploader/version.rb +3 -0
  48. data/lib/salebot_uploader.rb +62 -0
  49. 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
@@ -0,0 +1,3 @@
1
+ require 'salebot_uploader/storage/abstract'
2
+ require 'salebot_uploader/storage/file'
3
+ require 'salebot_uploader/storage/fog'