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,284 @@
1
+ module SalebotUploader
2
+
3
+ ##
4
+ # This module simplifies manipulation with vips by providing a set
5
+ # of convenient helper methods. If you want to use them, you'll need to
6
+ # require this file:
7
+ #
8
+ # require 'SalebotUploader/processing/vips'
9
+ #
10
+ # And then include it in your uploader:
11
+ #
12
+ # class MyUploader < SalebotUploader::Uploader::Base
13
+ # include SalebotUploader::Vips
14
+ # end
15
+ #
16
+ # You can now use the provided helpers:
17
+ #
18
+ # class MyUploader < SalebotUploader::Uploader::Base
19
+ # include SalebotUploader::Vips
20
+ #
21
+ # process :resize_to_fit => [200, 200]
22
+ # end
23
+ #
24
+ # Or create your own helpers with the powerful vips! method, which
25
+ # yields an ImageProcessing::Builder object. Check out the ImageProcessing
26
+ # docs at http://github.com/janko-m/image_processing and the list of all
27
+ # available Vips options at
28
+ # https://libvips.github.io/libvips/API/current/using-cli.html for more info.
29
+ #
30
+ # class MyUploader < SalebotUploader::Uploader::Base
31
+ # include SalebotUploader::Vips
32
+ #
33
+ # process :radial_blur => 10
34
+ #
35
+ # def radial_blur(amount)
36
+ # vips! do |builder|
37
+ # builder.radial_blur(amount)
38
+ # builder = yield(builder) if block_given?
39
+ # builder
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # === Note
45
+ #
46
+ # The ImageProcessing gem uses ruby-vips, a binding for the vips image
47
+ # library. You can find more information here:
48
+ #
49
+ # https://github.com/libvips/ruby-vips
50
+ #
51
+ #
52
+ module Vips
53
+ extend ActiveSupport::Concern
54
+
55
+ included do
56
+ require "image_processing/vips"
57
+ # We need to disable caching since we're editing images in place.
58
+ ::Vips.cache_set_max(0)
59
+ end
60
+
61
+ module ClassMethods
62
+ def convert(format)
63
+ process :convert => format
64
+ end
65
+
66
+ def resize_to_limit(width, height)
67
+ process :resize_to_limit => [width, height]
68
+ end
69
+
70
+ def resize_to_fit(width, height)
71
+ process :resize_to_fit => [width, height]
72
+ end
73
+
74
+ def resize_to_fill(width, height, gravity='centre')
75
+ process :resize_to_fill => [width, height, gravity]
76
+ end
77
+
78
+ def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil)
79
+ process :resize_and_pad => [width, height, background, gravity, alpha]
80
+ end
81
+ end
82
+
83
+ ##
84
+ # Changes the image encoding format to the given format
85
+ #
86
+ # See https://libvips.github.io/libvips/API/current/using-cli.html#using-command-line-conversion
87
+ #
88
+ # === Parameters
89
+ #
90
+ # [format (#to_s)] an abbreviation of the format
91
+ #
92
+ # === Yields
93
+ #
94
+ # [Vips::Image] additional manipulations to perform
95
+ #
96
+ # === Examples
97
+ #
98
+ # image.convert(:png)
99
+ #
100
+ def convert(format, page=nil)
101
+ vips! do |builder|
102
+ builder = builder.convert(format)
103
+ builder = builder.loader(page: page) if page
104
+ builder
105
+ end
106
+ end
107
+
108
+ ##
109
+ # Resize the image to fit within the specified dimensions while retaining
110
+ # the original aspect ratio. Will only resize the image if it is larger than the
111
+ # specified dimensions. The resulting image may be shorter or narrower than specified
112
+ # in the smaller dimension but will not be larger than the specified values.
113
+ #
114
+ # === Parameters
115
+ #
116
+ # [width (Integer)] the width to scale the image to
117
+ # [height (Integer)] the height to scale the image to
118
+ # [combine_options (Hash)] additional Vips options to apply before resizing
119
+ #
120
+ # === Yields
121
+ #
122
+ # [Vips::Image] additional manipulations to perform
123
+ #
124
+ def resize_to_limit(width, height, combine_options: {})
125
+ width, height = resolve_dimensions(width, height)
126
+
127
+ vips! do |builder|
128
+ builder.resize_to_limit(width, height)
129
+ .apply(combine_options)
130
+ end
131
+ end
132
+
133
+ ##
134
+ # Resize the image to fit within the specified dimensions while retaining
135
+ # the original aspect ratio. The image may be shorter or narrower than
136
+ # specified in the smaller dimension but will not be larger than the specified values.
137
+ #
138
+ # === Parameters
139
+ #
140
+ # [width (Integer)] the width to scale the image to
141
+ # [height (Integer)] the height to scale the image to
142
+ # [combine_options (Hash)] additional Vips options to apply before resizing
143
+ #
144
+ # === Yields
145
+ #
146
+ # [Vips::Image] additional manipulations to perform
147
+ #
148
+ def resize_to_fit(width, height, combine_options: {})
149
+ width, height = resolve_dimensions(width, height)
150
+
151
+ vips! do |builder|
152
+ builder.resize_to_fit(width, height)
153
+ .apply(combine_options)
154
+ end
155
+ end
156
+
157
+ ##
158
+ # Resize the image to fit within the specified dimensions while retaining
159
+ # the aspect ratio of the original image. If necessary, crop the image in the
160
+ # larger dimension.
161
+ #
162
+ # === Parameters
163
+ #
164
+ # [width (Integer)] the width to scale the image to
165
+ # [height (Integer)] the height to scale the image to
166
+ # [combine_options (Hash)] additional vips options to apply before resizing
167
+ #
168
+ # === Yields
169
+ #
170
+ # [Vips::Image] additional manipulations to perform
171
+ #
172
+ def resize_to_fill(width, height, _gravity = nil, combine_options: {})
173
+ width, height = resolve_dimensions(width, height)
174
+
175
+ vips! do |builder|
176
+ builder.resize_to_fill(width, height).apply(combine_options)
177
+ end
178
+ end
179
+
180
+ ##
181
+ # Resize the image to fit within the specified dimensions while retaining
182
+ # the original aspect ratio. If necessary, will pad the remaining area
183
+ # with the given color, which defaults to transparent (for gif and png,
184
+ # white for jpeg).
185
+ #
186
+ # See https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsCompassDirection
187
+ # for gravity options.
188
+ #
189
+ # === Parameters
190
+ #
191
+ # [width (Integer)] the width to scale the image to
192
+ # [height (Integer)] the height to scale the image to
193
+ # [background (List, nil)] the color of the background as a RGB, like [0, 255, 255], nil indicates transparent
194
+ # [gravity (String)] how to position the image
195
+ # [alpha (Boolean, nil)] pad the image with the alpha channel if supported
196
+ # [combine_options (Hash)] additional vips options to apply before resizing
197
+ #
198
+ # === Yields
199
+ #
200
+ # [Vips::Image] additional manipulations to perform
201
+ #
202
+ def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil, combine_options: {})
203
+ width, height = resolve_dimensions(width, height)
204
+
205
+ vips! do |builder|
206
+ builder.resize_and_pad(width, height, background: background, gravity: gravity, alpha: alpha)
207
+ .apply(combine_options)
208
+ end
209
+ end
210
+
211
+ ##
212
+ # Returns the width of the image in pixels.
213
+ #
214
+ # === Returns
215
+ #
216
+ # [Integer] the image's width in pixels
217
+ #
218
+ def width
219
+ vips_image.width
220
+ end
221
+
222
+ ##
223
+ # Returns the height of the image in pixels.
224
+ #
225
+ # === Returns
226
+ #
227
+ # [Integer] the image's height in pixels
228
+ #
229
+ def height
230
+ vips_image.height
231
+ end
232
+
233
+ # Process the image with vip, using the ImageProcessing gem. This
234
+ # method will build a "convert" vips command and execute it on the
235
+ # current image.
236
+ #
237
+ # === Gotcha
238
+ #
239
+ # This method assumes that the object responds to +current_path+.
240
+ # Any class that this module is mixed into must have a +current_path+ method.
241
+ # SalebotUploader::Uploader does, so you won't need to worry about this in
242
+ # most cases.
243
+ #
244
+ # === Yields
245
+ #
246
+ # [ImageProcessing::Builder] use it to define processing to be performed
247
+ #
248
+ # === Raises
249
+ #
250
+ # [SalebotUploader::ProcessingError] if processing failed.
251
+ def vips!
252
+ builder = ImageProcessing::Vips.source(current_path)
253
+ builder = yield(builder)
254
+
255
+ result = builder.call
256
+ result.close
257
+
258
+ FileUtils.mv result.path, current_path
259
+
260
+ if File.extname(result.path) != File.extname(current_path)
261
+ move_to = current_path.chomp(File.extname(current_path)) + File.extname(result.path)
262
+ file.content_type = Marcel::Magic.by_path(move_to).try(:type)
263
+ file.move_to(move_to, permissions, directory_permissions)
264
+ end
265
+ rescue ::Vips::Error
266
+ message = I18n.translate(:"errors.messages.processing_error")
267
+ raise SalebotUploader::ProcessingError, message
268
+ end
269
+
270
+ private
271
+
272
+ def resolve_dimensions(*dimensions)
273
+ dimensions.map do |value|
274
+ next value unless value.instance_of?(Proc)
275
+ value.arity >= 1 ? value.call(self) : value.call
276
+ end
277
+ end
278
+
279
+ def vips_image
280
+ ::Vips::Image.new_from_buffer(read, "")
281
+ end
282
+
283
+ end # Vips
284
+ end # SalebotUploader
@@ -0,0 +1,3 @@
1
+ require 'salebot_uploader/processing/rmagick'
2
+ require 'salebot_uploader/processing/mini_magick'
3
+ require 'salebot_uploader/processing/vips'
@@ -0,0 +1,357 @@
1
+ require 'pathname'
2
+ require 'active_support/core_ext/string/multibyte'
3
+ require 'marcel'
4
+
5
+ module SalebotUploader
6
+
7
+ ##
8
+ # SanitizedFile is a base class which provides a common API around all
9
+ # the different quirky Ruby File libraries. It has support for Tempfile,
10
+ # File, StringIO, Merb-style upload Hashes, as well as paths given as
11
+ # Strings and Pathnames.
12
+ #
13
+ # It's probably needlessly comprehensive and complex. Help is appreciated.
14
+ #
15
+ class SanitizedFile
16
+ include SalebotUploader::Utilities::FileName
17
+
18
+ attr_reader :file
19
+
20
+ class << self
21
+ attr_writer :sanitize_regexp
22
+
23
+ def sanitize_regexp
24
+ @sanitize_regexp ||= /[^[:word:]\.\-\+]/
25
+ end
26
+ end
27
+
28
+ def initialize(file)
29
+ self.file = file
30
+ @content = @content_type = nil
31
+ end
32
+
33
+ ##
34
+ # Returns the filename as is, without sanitizing it.
35
+ #
36
+ # === Returns
37
+ #
38
+ # [String] the unsanitized filename
39
+ #
40
+ def original_filename
41
+ return @original_filename if @original_filename
42
+
43
+ if @file && @file.respond_to?(:original_filename)
44
+ @file.original_filename
45
+ elsif path
46
+ File.basename(path)
47
+ end
48
+ end
49
+
50
+ ##
51
+ # Returns the filename, sanitized to strip out any evil characters.
52
+ #
53
+ # === Returns
54
+ #
55
+ # [String] the sanitized filename
56
+ #
57
+ def filename
58
+ sanitize(original_filename) if original_filename
59
+ end
60
+
61
+ alias_method :identifier, :filename
62
+
63
+ ##
64
+ # Returns the file's size.
65
+ #
66
+ # === Returns
67
+ #
68
+ # [Integer] the file's size in bytes.
69
+ #
70
+ def size
71
+ if is_path?
72
+ exists? ? File.size(path) : 0
73
+ elsif @file.respond_to?(:size)
74
+ @file.size
75
+ elsif path
76
+ exists? ? File.size(path) : 0
77
+ else
78
+ 0
79
+ end
80
+ end
81
+
82
+ ##
83
+ # Returns the full path to the file. If the file has no path, it will return nil.
84
+ #
85
+ # === Returns
86
+ #
87
+ # [String, nil] the path where the file is located.
88
+ #
89
+ def path
90
+ return if @file.blank?
91
+
92
+ if is_path?
93
+ File.expand_path(@file)
94
+ elsif @file.respond_to?(:path) && !@file.path.blank?
95
+ File.expand_path(@file.path)
96
+ end
97
+ end
98
+
99
+ ##
100
+ # === Returns
101
+ #
102
+ # [Boolean] whether the file is supplied as a pathname or string.
103
+ #
104
+ def is_path?
105
+ !!((@file.is_a?(String) || @file.is_a?(Pathname)) && !@file.blank?)
106
+ end
107
+
108
+ ##
109
+ # === Returns
110
+ #
111
+ # [Boolean] whether the file is valid and has a non-zero size
112
+ #
113
+ def empty?
114
+ @file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
115
+ end
116
+
117
+ ##
118
+ # === Returns
119
+ #
120
+ # [Boolean] Whether the file exists
121
+ #
122
+ def exists?
123
+ self.path.present? && File.exist?(self.path)
124
+ end
125
+
126
+ ##
127
+ # Returns the contents of the file.
128
+ #
129
+ # === Returns
130
+ #
131
+ # [String] contents of the file
132
+ #
133
+ def read(*args)
134
+ if @content
135
+ if args.empty?
136
+ @content
137
+ else
138
+ length, outbuf = args
139
+ raise ArgumentError, 'outbuf argument not supported since the content is already loaded' if outbuf
140
+
141
+ @content[0, length]
142
+ end
143
+ elsif is_path?
144
+ File.open(@file, 'rb') { |file| file.read(*args) }
145
+ else
146
+ @file.try(:rewind)
147
+ @content = @file.read(*args)
148
+ @file.try(:close) unless @file.class.ancestors.include?(::StringIO) || @file.try(:closed?)
149
+ @content
150
+ end
151
+ end
152
+
153
+ ##
154
+ # Moves the file to the given path
155
+ #
156
+ # === Parameters
157
+ #
158
+ # [new_path (String)] The path where the file should be moved.
159
+ # [permissions (Integer)] permissions to set on the file in its new location.
160
+ # [directory_permissions (Integer)] permissions to set on created directories.
161
+ #
162
+ def move_to(new_path, permissions=nil, directory_permissions=nil, keep_filename=false)
163
+ return if self.empty?
164
+
165
+ new_path = File.expand_path(new_path)
166
+
167
+ mkdir!(new_path, directory_permissions)
168
+ move!(new_path)
169
+ chmod!(new_path, permissions)
170
+ self.file = {tempfile: new_path, filename: keep_filename ? original_filename : nil, content_type: declared_content_type}
171
+ self
172
+ end
173
+
174
+ ##
175
+ # Helper to move file to new path.
176
+ #
177
+ def move!(new_path)
178
+ if exists?
179
+ FileUtils.mv(path, new_path) unless File.identical?(new_path, path)
180
+ else
181
+ File.open(new_path, 'wb') { |f| f.write(read) }
182
+ end
183
+ end
184
+
185
+ ##
186
+ # Creates a copy of this file and moves it to the given path. Returns the copy.
187
+ #
188
+ # === Parameters
189
+ #
190
+ # [new_path (String)] The path where the file should be copied to.
191
+ # [permissions (Integer)] permissions to set on the copy
192
+ # [directory_permissions (Integer)] permissions to set on created directories.
193
+ #
194
+ # === Returns
195
+ #
196
+ # @return [SalebotUploader::SanitizedFile] the location where the file will be stored.
197
+ #
198
+ def copy_to(new_path, permissions=nil, directory_permissions=nil)
199
+ return if self.empty?
200
+
201
+ new_path = File.expand_path(new_path)
202
+
203
+ mkdir!(new_path, directory_permissions)
204
+ copy!(new_path)
205
+ chmod!(new_path, permissions)
206
+ self.class.new({tempfile: new_path, content_type: declared_content_type})
207
+ end
208
+
209
+ ##
210
+ # Helper to create copy of file in new path.
211
+ #
212
+ def copy!(new_path)
213
+ if exists?
214
+ FileUtils.cp(path, new_path) unless new_path == path
215
+ else
216
+ File.open(new_path, 'wb') { |f| f.write(read) }
217
+ end
218
+ end
219
+
220
+ ##
221
+ # Removes the file from the filesystem.
222
+ #
223
+ def delete
224
+ FileUtils.rm(self.path) if exists?
225
+ end
226
+
227
+ ##
228
+ # Returns a File object, or nil if it does not exist.
229
+ #
230
+ # === Returns
231
+ #
232
+ # [File] a File object representing the SanitizedFile
233
+ #
234
+ def to_file
235
+ return @file if @file.is_a?(File)
236
+
237
+ File.open(path, 'rb') if exists?
238
+ end
239
+
240
+ ##
241
+ # Returns the content type of the file.
242
+ #
243
+ # === Returns
244
+ #
245
+ # [String] the content type of the file
246
+ #
247
+ def content_type
248
+ @content_type ||=
249
+ identified_content_type ||
250
+ declared_content_type ||
251
+ guessed_safe_content_type ||
252
+ Marcel::MimeType::BINARY
253
+ end
254
+
255
+ ##
256
+ # Sets the content type of the file.
257
+ #
258
+ # === Returns
259
+ #
260
+ # [String] the content type of the file
261
+ #
262
+ def content_type=(type)
263
+ @content_type = type
264
+ end
265
+
266
+ ##
267
+ # Used to sanitize the file name. Public to allow overriding for non-latin characters.
268
+ #
269
+ # === Returns
270
+ #
271
+ # [Regexp] the regexp for sanitizing the file name
272
+ #
273
+ def sanitize_regexp
274
+ SalebotUploader::SanitizedFile.sanitize_regexp
275
+ end
276
+
277
+ private
278
+
279
+ def file=(file)
280
+ if file.is_a?(Hash)
281
+ @file = file['tempfile'] || file[:tempfile]
282
+ @original_filename = file['filename'] || file[:filename]
283
+ @declared_content_type = file['content_type'] || file[:content_type] || file['type'] || file[:type]
284
+ else
285
+ @file = file
286
+ @original_filename = nil
287
+ @declared_content_type = nil
288
+ end
289
+ end
290
+
291
+ # create the directory if it doesn't exist
292
+ def mkdir!(path, directory_permissions)
293
+ options = {}
294
+ options[:mode] = directory_permissions if directory_permissions
295
+ FileUtils.mkdir_p(File.dirname(path), **options) unless File.exist?(File.dirname(path))
296
+ end
297
+
298
+ def chmod!(path, permissions)
299
+ File.chmod(permissions, path) if permissions
300
+ end
301
+
302
+ # Sanitize the filename, to prevent hacking
303
+ def sanitize(name)
304
+ name = name.scrub
305
+ name = name.tr('\\', '/') # work-around for IE
306
+ name = File.basename(name)
307
+ name = name.gsub(sanitize_regexp, '_')
308
+ name = "_#{name}" if name =~ /\A\.+\z/
309
+ name = 'unnamed' if name.size.zero?
310
+ name.mb_chars.to_s
311
+ end
312
+
313
+ def declared_content_type
314
+ @declared_content_type ||
315
+ if @file.respond_to?(:content_type) && @file.content_type
316
+ @file.content_type.to_s.chomp
317
+ end
318
+ end
319
+
320
+ # Guess content type from its file extension. Limit what to be returned to prevent spoofing.
321
+ def guessed_safe_content_type
322
+ return unless path
323
+
324
+ type = Marcel::Magic.by_path(original_filename).to_s
325
+ type if type.start_with?('text/') || type.start_with?('application/json')
326
+ end
327
+
328
+ def identified_content_type
329
+ with_io do |io|
330
+ mimetype_by_magic = Marcel::Magic.by_magic(io)
331
+ mimetype_by_path = Marcel::Magic.by_path(path)
332
+
333
+ return nil if mimetype_by_magic.nil?
334
+
335
+ if mimetype_by_path&.child_of?(mimetype_by_magic.type)
336
+ mimetype_by_path.type
337
+ else
338
+ mimetype_by_magic.type
339
+ end
340
+ end
341
+ rescue Errno::ENOENT
342
+ nil
343
+ end
344
+
345
+ def with_io(&block)
346
+ if file.is_a?(IO)
347
+ begin
348
+ yield file
349
+ ensure
350
+ file.try(:rewind)
351
+ end
352
+ elsif path
353
+ File.open(path, &block)
354
+ end
355
+ end
356
+ end # SanitizedFile
357
+ end # SalebotUploader
@@ -0,0 +1,41 @@
1
+ module SalebotUploader
2
+ module Storage
3
+
4
+ ##
5
+ # This file serves mostly as a specification for Storage engines. There is no requirement
6
+ # that storage engines must be a subclass of this class.
7
+ #
8
+ class Abstract
9
+
10
+ attr_reader :uploader
11
+
12
+ def initialize(uploader)
13
+ @uploader = uploader
14
+ end
15
+
16
+ def identifier
17
+ uploader.deduplicated_filename
18
+ end
19
+
20
+ def store!(file); end
21
+
22
+ def retrieve!(identifier); end
23
+
24
+ def cache!(new_file)
25
+ raise NotImplementedError, "Need to implement #cache! if you want to use #{self.class.name} as a cache storage."
26
+ end
27
+
28
+ def retrieve_from_cache!(identifier)
29
+ raise NotImplementedError, "Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage."
30
+ end
31
+
32
+ def delete_dir!(path)
33
+ raise NotImplementedError, "Need to implement #delete_dir! if you want to use #{self.class.name} as a cache storage."
34
+ end
35
+
36
+ def clean_cache!(seconds)
37
+ raise NotImplementedError, "Need to implement #clean_cache! if you want to use #{self.class.name} as a cache storage."
38
+ end
39
+ end # Abstract
40
+ end # Storage
41
+ end # SalebotUploader