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.
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'