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,350 @@
1
+ require "active_support/core_ext/object/deep_dup"
2
+
3
+ module SalebotUploader
4
+ module Uploader
5
+ module Versions
6
+ class Builder
7
+ def initialize(name)
8
+ @name = name
9
+ @options = {}
10
+ @blocks = []
11
+ @klass = nil
12
+ end
13
+
14
+ def configure(options, &block)
15
+ @options.merge!(options)
16
+ @blocks << block if block
17
+ @klass = nil
18
+ end
19
+
20
+ def build(superclass)
21
+ return @klass if @klass
22
+ @klass = Class.new(superclass)
23
+ superclass.const_set("#{@name.to_s.camelize}VersionUploader", @klass)
24
+
25
+ @klass.version_names += [@name]
26
+ @klass.versions = {}
27
+ @klass.processors = []
28
+ @klass.version_options = @options
29
+ @klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
30
+ # Define the enable_processing method for versions so they get the
31
+ # value from the parent class unless explicitly overwritten
32
+ def self.enable_processing(value=nil)
33
+ self.enable_processing = value if value
34
+ if defined?(@enable_processing) && !@enable_processing.nil?
35
+ @enable_processing
36
+ else
37
+ superclass.enable_processing
38
+ end
39
+ end
40
+
41
+ # Regardless of what is set in the parent uploader, do not enforce the
42
+ # move_to_cache config option on versions because it moves the original
43
+ # file to the version's target file.
44
+ #
45
+ # If you want to enforce this setting on versions, override this method
46
+ # in each version:
47
+ #
48
+ # version :thumb do
49
+ # def move_to_cache
50
+ # true
51
+ # end
52
+ # end
53
+ #
54
+ def move_to_cache
55
+ false
56
+ end
57
+
58
+ # Need to rely on the parent version's identifier, as versions don't have its own one.
59
+ def identifier
60
+ parent_version.identifier
61
+ end
62
+ RUBY
63
+ @blocks.each { |block| @klass.class_eval(&block) }
64
+ @klass
65
+ end
66
+
67
+ def deep_dup
68
+ other = dup
69
+ other.instance_variable_set(:@blocks, @blocks.dup)
70
+ other
71
+ end
72
+
73
+ def method_missing(name, *args)
74
+ super
75
+ rescue NoMethodError => e
76
+ raise e.exception <<~ERROR
77
+ #{e.message}
78
+ If you're trying to configure a version, do it inside a block like `version(:thumb) { self.#{name} #{args.map(&:inspect).join(', ')} }`.
79
+ ERROR
80
+ end
81
+
82
+ def respond_to_missing?(*)
83
+ super
84
+ end
85
+ end
86
+
87
+ extend ActiveSupport::Concern
88
+
89
+ include SalebotUploader::Uploader::Callbacks
90
+
91
+ included do
92
+ class_attribute :versions, :version_names, :version_options, :instance_reader => false, :instance_writer => false
93
+
94
+ self.versions = {}
95
+ self.version_names = []
96
+
97
+ attr_accessor :parent_version
98
+
99
+ after :cache, :cache_versions!
100
+ after :store, :store_versions!
101
+ after :remove, :remove_versions!
102
+ after :retrieve_from_cache, :retrieve_versions_from_cache!
103
+ after :retrieve_from_store, :retrieve_versions_from_store!
104
+
105
+ prepend Module.new {
106
+ def initialize(*)
107
+ super
108
+ @versions = nil
109
+ end
110
+ }
111
+ end
112
+
113
+ module ClassMethods
114
+
115
+ ##
116
+ # Adds a new version to this uploader
117
+ #
118
+ # === Parameters
119
+ #
120
+ # [name (#to_sym)] name of the version
121
+ # [options (Hash)] optional options hash
122
+ # [&block (Proc)] a block to eval on this version of the uploader
123
+ #
124
+ # === Examples
125
+ #
126
+ # class MyUploader < SalebotUploader::Uploader::Base
127
+ #
128
+ # version :thumb do
129
+ # process :scale => [200, 200]
130
+ # end
131
+ #
132
+ # version :preview, :if => :image? do
133
+ # process :scale => [200, 200]
134
+ # end
135
+ #
136
+ # version :square, :unless => :invalid_image_type? do
137
+ # process :scale => [100, 100]
138
+ # end
139
+ #
140
+ # end
141
+ #
142
+ def version(name, options = {}, &block)
143
+ name = name.to_sym
144
+ versions[name] ||= Builder.new(name)
145
+ versions[name].configure(options, &block)
146
+
147
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
148
+ def #{name}
149
+ versions[:#{name}]
150
+ end
151
+ RUBY
152
+
153
+ versions[name]
154
+ end
155
+
156
+ private
157
+
158
+ def inherited(subclass)
159
+ # To prevent subclass version changes affecting superclass versions
160
+ subclass.versions = versions.deep_dup
161
+ super
162
+ end
163
+ end # ClassMethods
164
+
165
+ ##
166
+ # Returns a hash mapping the name of each version of the uploader to an instance of it
167
+ #
168
+ # === Returns
169
+ #
170
+ # [Hash{Symbol => SalebotUploader::Uploader}] a list of uploader instances
171
+ #
172
+ def versions
173
+ return @versions if @versions
174
+ @versions = {}
175
+ self.class.versions.each do |name, version|
176
+ @versions[name] = version.build(self.class).new(model, mounted_as)
177
+ @versions[name].parent_version = self
178
+ end
179
+ @versions
180
+ end
181
+
182
+ ##
183
+ # === Returns
184
+ #
185
+ # [String] the name of this version of the uploader
186
+ #
187
+ def version_name
188
+ self.class.version_names.join('_').to_sym unless self.class.version_names.blank?
189
+ end
190
+
191
+ ##
192
+ #
193
+ # === Parameters
194
+ #
195
+ # [name (#to_sym)] name of the version
196
+ #
197
+ # === Returns
198
+ #
199
+ # [Boolean] True when the version exists according to its :if or :unless condition
200
+ #
201
+ def version_exists?(name)
202
+ name = name.to_sym
203
+
204
+ return false unless versions.has_key?(name)
205
+
206
+ if_condition = versions[name].class.version_options[:if]
207
+ unless_condition = versions[name].class.version_options[:unless]
208
+
209
+ if if_condition
210
+ if if_condition.respond_to?(:call)
211
+ if_condition.call(self, :version => name, :file => file)
212
+ else
213
+ send(if_condition, file)
214
+ end
215
+ elsif unless_condition
216
+ if unless_condition.respond_to?(:call)
217
+ !unless_condition.call(self, :version => name, :file => file)
218
+ else
219
+ !send(unless_condition, file)
220
+ end
221
+ else
222
+ true
223
+ end
224
+ end
225
+
226
+ ##
227
+ # Copies the parent's cache_id when caching a version file.
228
+ # This behavior is not essential but it makes easier to understand
229
+ # that the cached files are generated by the single upload attempt.
230
+ #
231
+ def cache!(*args)
232
+ self.cache_id = parent_version.cache_id if parent_version
233
+
234
+ super
235
+ end
236
+
237
+ ##
238
+ # When given a version name as a parameter, will return the url for that version
239
+ # This also works with nested versions.
240
+ # When given a query hash as a parameter, will return the url with signature that contains query params
241
+ # Query hash only works with AWS (S3 storage).
242
+ #
243
+ # === Example
244
+ #
245
+ # my_uploader.url # => /path/to/my/uploader.gif
246
+ # my_uploader.url(:thumb) # => /path/to/my/thumb_uploader.gif
247
+ # my_uploader.url(:thumb, :small) # => /path/to/my/thumb_small_uploader.gif
248
+ # my_uploader.url(:query => {"response-content-disposition" => "attachment"})
249
+ # my_uploader.url(:version, :sub_version, :query => {"response-content-disposition" => "attachment"})
250
+ #
251
+ # === Parameters
252
+ #
253
+ # [*args (Symbol)] any number of versions
254
+ # OR/AND
255
+ # [Hash] query params
256
+ #
257
+ # === Returns
258
+ #
259
+ # [String] the location where this file is accessible via a url
260
+ #
261
+ def url(*args)
262
+ if (version = args.first) && version.respond_to?(:to_sym)
263
+ raise ArgumentError, "Version #{version} doesn't exist!" if versions[version.to_sym].nil?
264
+ # recursively proxy to version
265
+ versions[version.to_sym].url(*args[1..-1])
266
+ elsif args.first
267
+ super(args.first)
268
+ else
269
+ super
270
+ end
271
+ end
272
+
273
+ ##
274
+ # Recreate versions and reprocess them. This can be used to recreate
275
+ # versions if their parameters somehow have changed.
276
+ #
277
+ def recreate_versions!(*names)
278
+ # As well as specified versions, we need to reprocess versions
279
+ # that are the source of another version.
280
+
281
+ self.cache_id = SalebotUploader.generate_cache_id
282
+ derived_versions.each_value do |v|
283
+ v.cache!(file) if names.empty? || !(v.descendant_version_names & names).empty?
284
+ end
285
+ active_versions.each do |name, v|
286
+ v.store! if names.empty? || names.include?(name)
287
+ end
288
+ ensure
289
+ @cache_id = nil
290
+ end
291
+
292
+ protected
293
+
294
+ def descendant_version_names
295
+ [version_name] + derived_versions.flat_map do |name, version|
296
+ version.descendant_version_names
297
+ end
298
+ end
299
+
300
+ def active_versions
301
+ versions.select do |name, uploader|
302
+ version_exists?(name)
303
+ end
304
+ end
305
+
306
+ private
307
+
308
+ def derived_versions
309
+ active_versions.reject do |name, v|
310
+ v.class.version_options[:from_version]
311
+ end.merge(active_sibling_versions.select do |name, v|
312
+ v.class.version_options[:from_version] == self.class.version_names.last
313
+ end)
314
+ end
315
+
316
+ def active_sibling_versions
317
+ parent_version&.active_versions || {}
318
+ end
319
+
320
+ def full_filename(for_file)
321
+ [version_name, super(for_file)].compact.join('_')
322
+ end
323
+
324
+ def full_original_filename
325
+ [version_name, super].compact.join('_')
326
+ end
327
+
328
+ def cache_versions!(new_file)
329
+ derived_versions.each_value { |v| v.cache!(new_file) }
330
+ end
331
+
332
+ def store_versions!(new_file)
333
+ active_versions.each_value { |v| v.store!(new_file) }
334
+ end
335
+
336
+ def remove_versions!
337
+ versions.each_value { |v| v.remove! }
338
+ end
339
+
340
+ def retrieve_versions_from_cache!(cache_name)
341
+ active_versions.each_value { |v| v.retrieve_from_cache!(cache_name) }
342
+ end
343
+
344
+ def retrieve_versions_from_store!(identifier)
345
+ active_versions.each_value { |v| v.retrieve_from_store!(identifier) }
346
+ end
347
+
348
+ end # Versions
349
+ end # Uploader
350
+ end # SalebotUploader
@@ -0,0 +1,53 @@
1
+ require 'salebot_uploader/uploader/configuration'
2
+ require 'salebot_uploader/uploader/callbacks'
3
+ require 'salebot_uploader/uploader/proxy'
4
+ require 'salebot_uploader/uploader/url'
5
+ require 'salebot_uploader/uploader/mountable'
6
+ require 'salebot_uploader/uploader/cache'
7
+ require 'salebot_uploader/uploader/store'
8
+ require 'salebot_uploader/uploader/download'
9
+ require 'salebot_uploader/uploader/remove'
10
+ require 'salebot_uploader/uploader/extension_allowlist'
11
+ require 'salebot_uploader/uploader/extension_denylist'
12
+ require 'salebot_uploader/uploader/content_type_allowlist'
13
+ require 'salebot_uploader/uploader/content_type_denylist'
14
+ require 'salebot_uploader/uploader/file_size'
15
+ require 'salebot_uploader/uploader/dimension'
16
+ require 'salebot_uploader/uploader/processing'
17
+ require 'salebot_uploader/uploader/versions'
18
+ require 'salebot_uploader/uploader/default_url'
19
+
20
+ require 'salebot_uploader/uploader/serialization'
21
+
22
+ module SalebotUploader
23
+
24
+ ##
25
+ # See SalebotUploader::Uploader::Base
26
+ #
27
+ module Uploader
28
+ class Base
29
+ attr_reader :file
30
+
31
+ include SalebotUploader::Uploader::Configuration
32
+ include SalebotUploader::Uploader::Callbacks
33
+ include SalebotUploader::Uploader::Proxy
34
+ include SalebotUploader::Uploader::Url
35
+ include SalebotUploader::Uploader::Mountable
36
+ include SalebotUploader::Uploader::Cache
37
+ include SalebotUploader::Uploader::Store
38
+ include SalebotUploader::Uploader::Download
39
+ include SalebotUploader::Uploader::Remove
40
+ include SalebotUploader::Uploader::ExtensionAllowlist
41
+ include SalebotUploader::Uploader::ExtensionDenylist
42
+ include SalebotUploader::Uploader::ContentTypeAllowlist
43
+ include SalebotUploader::Uploader::ContentTypeDenylist
44
+ include SalebotUploader::Uploader::FileSize
45
+ include SalebotUploader::Uploader::Dimension
46
+ include SalebotUploader::Uploader::Processing
47
+ include SalebotUploader::Uploader::Versions
48
+ include SalebotUploader::Uploader::DefaultUrl
49
+ include SalebotUploader::Uploader::Serialization
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,47 @@
1
+ module SalebotUploader
2
+ module Utilities
3
+ module FileName
4
+
5
+ ##
6
+ # Returns the part of the filename before the extension. So if a file is called 'test.jpeg'
7
+ # this would return 'test'
8
+ #
9
+ # === Returns
10
+ #
11
+ # [String] the first part of the filename
12
+ #
13
+ def basename
14
+ split_extension(filename)[0] if filename
15
+ end
16
+
17
+ ##
18
+ # Returns the file extension
19
+ #
20
+ # === Returns
21
+ #
22
+ # [String] extension of file or "" if the file has no extension
23
+ #
24
+ def extension
25
+ split_extension(filename)[1] if filename
26
+ end
27
+
28
+ private
29
+
30
+ def split_extension(filename)
31
+ # regular expressions to try for identifying extensions
32
+ extension_matchers = [
33
+ /\A(.+)\.(tar\.([glx]?z|bz2))\z/, # matches "something.tar.gz"
34
+ /\A(.+)\.([^\.]+)\z/ # matches "something.jpg"
35
+ ]
36
+
37
+ extension_matchers.each do |regexp|
38
+ if filename =~ regexp
39
+ return $1, $2
40
+ end
41
+ end
42
+
43
+ [filename, ""] # In case we weren't able to split the extension
44
+ end
45
+ end # FileName
46
+ end # Utilities
47
+ end # SalebotUploader
@@ -0,0 +1,26 @@
1
+ require 'uri'
2
+
3
+ module SalebotUploader
4
+ module Utilities
5
+ module Uri
6
+ # based on Ruby < 2.0's URI.encode
7
+ PATH_SAFE = URI::REGEXP::PATTERN::UNRESERVED + '\/'
8
+ PATH_UNSAFE = Regexp.new("[^#{PATH_SAFE}]", false)
9
+ NON_ASCII = /[^[:ascii:]]/
10
+
11
+ private
12
+
13
+ def encode_path(path)
14
+ URI::DEFAULT_PARSER.escape(path, PATH_UNSAFE)
15
+ end
16
+
17
+ def encode_non_ascii(str)
18
+ URI::DEFAULT_PARSER.escape(str, NON_ASCII)
19
+ end
20
+
21
+ def decode_uri(str)
22
+ URI::DEFAULT_PARSER.unescape(str)
23
+ end
24
+ end # Uri
25
+ end # Utilities
26
+ end # SalebotUploader
@@ -0,0 +1,7 @@
1
+ require 'salebot_uploader/utilities/uri'
2
+ require 'salebot_uploader/utilities/file_name'
3
+
4
+ module SalebotUploader
5
+ module Utilities
6
+ end
7
+ end
@@ -0,0 +1,76 @@
1
+ require 'active_model/validator'
2
+ require 'active_support/concern'
3
+
4
+ module SalebotUploader
5
+
6
+ # == Active Model Presence Validator
7
+ module Validations
8
+ module ActiveModel
9
+ extend ActiveSupport::Concern
10
+
11
+ class ProcessingValidator < ::ActiveModel::EachValidator
12
+
13
+ def validate_each(record, attribute, value)
14
+ record.__send__("#{attribute}_processing_errors").each do |e|
15
+ record.errors.add(attribute, :salebot_uploader_processing_error, message: (e.message != e.class.to_s) && e.message)
16
+ end
17
+ end
18
+ end
19
+
20
+ class IntegrityValidator < ::ActiveModel::EachValidator
21
+
22
+ def validate_each(record, attribute, value)
23
+ record.__send__("#{attribute}_integrity_errors").each do |e|
24
+ record.errors.add(attribute, :salebot_uploader_integrity_error, message: (e.message != e.class.to_s) && e.message)
25
+ end
26
+ end
27
+ end
28
+
29
+ class DownloadValidator < ::ActiveModel::EachValidator
30
+
31
+ def validate_each(record, attribute, value)
32
+ record.__send__("#{attribute}_download_errors").each do |e|
33
+ record.errors.add(attribute, :salebot_uploader_download_error, message: (e.message != e.class.to_s) && e.message)
34
+ end
35
+ end
36
+ end
37
+
38
+ module HelperMethods
39
+
40
+ ##
41
+ # Makes the record invalid if the file couldn't be uploaded due to an integrity error
42
+ #
43
+ # Accepts the usual parameters for validations in Rails (:if, :unless, etc...)
44
+ #
45
+ def validates_integrity_of(*attr_names)
46
+ validates_with IntegrityValidator, _merge_attributes(attr_names)
47
+ end
48
+
49
+ ##
50
+ # Makes the record invalid if the file couldn't be processed (assuming the process failed
51
+ # with a SalebotUploader::ProcessingError)
52
+ #
53
+ # Accepts the usual parameters for validations in Rails (:if, :unless, etc...)
54
+ #
55
+ def validates_processing_of(*attr_names)
56
+ validates_with ProcessingValidator, _merge_attributes(attr_names)
57
+ end
58
+
59
+ #
60
+ ##
61
+ # Makes the record invalid if the remote file couldn't be downloaded
62
+ #
63
+ # Accepts the usual parameters for validations in Rails (:if, :unless, etc...)
64
+ #
65
+ def validates_download_of(*attr_names)
66
+ validates_with DownloadValidator, _merge_attributes(attr_names)
67
+ end
68
+ end
69
+
70
+ included do
71
+ extend HelperMethods
72
+ include HelperMethods
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ module SalebotUploader
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,62 @@
1
+ require 'fileutils'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'active_support/core_ext/object/try'
4
+ require 'active_support/core_ext/class/attribute'
5
+ require 'active_support/concern'
6
+
7
+ module SalebotUploader
8
+ class << self
9
+ attr_accessor :root, :base_path
10
+ attr_writer :tmp_path
11
+
12
+ def configure(&block)
13
+ SalebotUploader::Uploader::Base.configure(&block)
14
+ end
15
+
16
+ def clean_cached_files!(seconds=60*60*24)
17
+ SalebotUploader::Uploader::Base.clean_cached_files!(seconds)
18
+ end
19
+
20
+ def tmp_path
21
+ @tmp_path ||= File.expand_path(File.join('..', 'tmp'), root)
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ if defined?(Rails)
28
+ module SalebotUploader
29
+ class Railtie < Rails::Railtie
30
+ initializer 'salebot_uploader.setup_paths' do |app|
31
+ SalebotUploader.root = Rails.root.join(Rails.public_path).to_s
32
+ SalebotUploader.base_path = ENV['RAILS_RELATIVE_URL_ROOT']
33
+ available_locales = Array(app.config.i18n.available_locales || [])
34
+ if available_locales.blank? || available_locales.include?(:en)
35
+ I18n.load_path.prepend(File.join(File.dirname(__FILE__), 'salebot_uploader', 'locale', 'en.yml'))
36
+ end
37
+ end
38
+
39
+ initializer 'salebot_uploader.active_record' do
40
+ ActiveSupport.on_load :active_record do
41
+ require 'salebot_uploader/orm/activerecord'
42
+ end
43
+ end
44
+
45
+ config.before_eager_load do
46
+ SalebotUploader::Storage::Fog.eager_load
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ require 'salebot_uploader/utilities'
53
+ require 'salebot_uploader/error'
54
+ require 'salebot_uploader/sanitized_file'
55
+ require 'salebot_uploader/mounter'
56
+ require 'salebot_uploader/mount'
57
+ require 'salebot_uploader/processing'
58
+ require 'salebot_uploader/version'
59
+ require 'salebot_uploader/storage'
60
+ require 'salebot_uploader/uploader'
61
+ require 'salebot_uploader/compatibility/paperclip'
62
+ require 'salebot_uploader/test/matchers'