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