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