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.
- checksums.yaml +7 -0
- data/README.md +0 -0
- data/lib/generators/templates/uploader.rb.erb +9 -0
- data/lib/generators/uploader_generator.rb +7 -0
- data/lib/salebot_uploader/compatibility/paperclip.rb +104 -0
- data/lib/salebot_uploader/downloader/base.rb +101 -0
- data/lib/salebot_uploader/downloader/remote_file.rb +68 -0
- data/lib/salebot_uploader/error.rb +8 -0
- data/lib/salebot_uploader/locale/en.yml +17 -0
- data/lib/salebot_uploader/mount.rb +446 -0
- data/lib/salebot_uploader/mounter.rb +255 -0
- data/lib/salebot_uploader/orm/activerecord.rb +68 -0
- data/lib/salebot_uploader/processing/mini_magick.rb +194 -0
- data/lib/salebot_uploader/processing/rmagick.rb +402 -0
- data/lib/salebot_uploader/processing/vips.rb +284 -0
- data/lib/salebot_uploader/processing.rb +3 -0
- data/lib/salebot_uploader/sanitized_file.rb +357 -0
- data/lib/salebot_uploader/storage/abstract.rb +41 -0
- data/lib/salebot_uploader/storage/file.rb +124 -0
- data/lib/salebot_uploader/storage/fog.rb +547 -0
- data/lib/salebot_uploader/storage.rb +3 -0
- data/lib/salebot_uploader/test/matchers.rb +398 -0
- data/lib/salebot_uploader/uploader/cache.rb +223 -0
- data/lib/salebot_uploader/uploader/callbacks.rb +33 -0
- data/lib/salebot_uploader/uploader/configuration.rb +184 -0
- data/lib/salebot_uploader/uploader/content_type_allowlist.rb +61 -0
- data/lib/salebot_uploader/uploader/content_type_denylist.rb +62 -0
- data/lib/salebot_uploader/uploader/default_url.rb +17 -0
- data/lib/salebot_uploader/uploader/dimension.rb +66 -0
- data/lib/salebot_uploader/uploader/download.rb +24 -0
- data/lib/salebot_uploader/uploader/extension_allowlist.rb +63 -0
- data/lib/salebot_uploader/uploader/extension_denylist.rb +64 -0
- data/lib/salebot_uploader/uploader/file_size.rb +43 -0
- data/lib/salebot_uploader/uploader/mountable.rb +44 -0
- data/lib/salebot_uploader/uploader/processing.rb +125 -0
- data/lib/salebot_uploader/uploader/proxy.rb +99 -0
- data/lib/salebot_uploader/uploader/remove.rb +21 -0
- data/lib/salebot_uploader/uploader/serialization.rb +28 -0
- data/lib/salebot_uploader/uploader/store.rb +142 -0
- data/lib/salebot_uploader/uploader/url.rb +44 -0
- data/lib/salebot_uploader/uploader/versions.rb +350 -0
- data/lib/salebot_uploader/uploader.rb +53 -0
- data/lib/salebot_uploader/utilities/file_name.rb +47 -0
- data/lib/salebot_uploader/utilities/uri.rb +26 -0
- data/lib/salebot_uploader/utilities.rb +7 -0
- data/lib/salebot_uploader/validations/active_model.rb +76 -0
- data/lib/salebot_uploader/version.rb +3 -0
- data/lib/salebot_uploader.rb +62 -0
- metadata +392 -0
@@ -0,0 +1,398 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Test
|
3
|
+
|
4
|
+
##
|
5
|
+
# These are some matchers that can be used in RSpec specs, to simplify the testing
|
6
|
+
# of uploaders.
|
7
|
+
#
|
8
|
+
module Matchers
|
9
|
+
|
10
|
+
class BeIdenticalTo # :nodoc:
|
11
|
+
def initialize(expected)
|
12
|
+
@expected = expected
|
13
|
+
end
|
14
|
+
|
15
|
+
def matches?(actual)
|
16
|
+
@actual = actual
|
17
|
+
FileUtils.identical?(@actual, @expected)
|
18
|
+
end
|
19
|
+
|
20
|
+
def failure_message
|
21
|
+
"expected #{@actual.inspect} to be identical to #{@expected.inspect}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def failure_message_when_negated
|
25
|
+
"expected #{@actual.inspect} to not be identical to #{@expected.inspect}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def description
|
29
|
+
"be identical to #{@expected.inspect}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# RSpec 2 compatibility:
|
33
|
+
alias_method :negative_failure_message, :failure_message_when_negated
|
34
|
+
end
|
35
|
+
|
36
|
+
def be_identical_to(expected)
|
37
|
+
BeIdenticalTo.new(expected)
|
38
|
+
end
|
39
|
+
|
40
|
+
class HavePermissions # :nodoc:
|
41
|
+
def initialize(expected)
|
42
|
+
@expected = expected
|
43
|
+
end
|
44
|
+
|
45
|
+
def matches?(actual)
|
46
|
+
@actual = actual
|
47
|
+
# Satisfy expectation here. Return false or raise an error if it's not met.
|
48
|
+
(File.stat(@actual.path).mode & 0o777) == @expected
|
49
|
+
end
|
50
|
+
|
51
|
+
def failure_message
|
52
|
+
"expected #{@actual.current_path.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0o777).to_s(8)}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def failure_message_when_negated
|
56
|
+
"expected #{@actual.current_path.inspect} not to have permissions #{@expected.to_s(8)}, but it did"
|
57
|
+
end
|
58
|
+
|
59
|
+
def description
|
60
|
+
"have permissions #{@expected.to_s(8)}"
|
61
|
+
end
|
62
|
+
|
63
|
+
# RSpec 2 compatibility:
|
64
|
+
alias_method :negative_failure_message, :failure_message_when_negated
|
65
|
+
end
|
66
|
+
|
67
|
+
def have_permissions(expected)
|
68
|
+
HavePermissions.new(expected)
|
69
|
+
end
|
70
|
+
|
71
|
+
class HaveDirectoryPermissions # :nodoc:
|
72
|
+
def initialize(expected)
|
73
|
+
@expected = expected
|
74
|
+
end
|
75
|
+
|
76
|
+
def matches?(actual)
|
77
|
+
@actual = actual
|
78
|
+
# Satisfy expectation here. Return false or raise an error if it's not met.
|
79
|
+
(File.stat(File.dirname(@actual.path)).mode & 0o777) == @expected
|
80
|
+
end
|
81
|
+
|
82
|
+
def failure_message
|
83
|
+
"expected #{File.dirname @actual.current_path.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0o777).to_s(8)}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def failure_message_when_negated
|
87
|
+
"expected #{File.dirname @actual.current_path.inspect} not to have permissions #{@expected.to_s(8)}, but it did"
|
88
|
+
end
|
89
|
+
|
90
|
+
def description
|
91
|
+
"have permissions #{@expected.to_s(8)}"
|
92
|
+
end
|
93
|
+
|
94
|
+
# RSpec 2 compatibility:
|
95
|
+
alias_method :negative_failure_message, :failure_message_when_negated
|
96
|
+
end
|
97
|
+
|
98
|
+
def have_directory_permissions(expected)
|
99
|
+
HaveDirectoryPermissions.new(expected)
|
100
|
+
end
|
101
|
+
|
102
|
+
class BeNoLargerThan # :nodoc:
|
103
|
+
def initialize(width, height)
|
104
|
+
@width, @height = width, height
|
105
|
+
end
|
106
|
+
|
107
|
+
def matches?(actual)
|
108
|
+
@actual = actual
|
109
|
+
# Satisfy expectation here. Return false or raise an error if it's not met.
|
110
|
+
image = ImageLoader.load_image(@actual.current_path)
|
111
|
+
@actual_width = image.width
|
112
|
+
@actual_height = image.height
|
113
|
+
@actual_width <= @width && @actual_height <= @height
|
114
|
+
end
|
115
|
+
|
116
|
+
def failure_message
|
117
|
+
"expected #{@actual.current_path.inspect} to be no larger than #{@width} by #{@height}, but it was #{@actual_width} by #{@actual_height}."
|
118
|
+
end
|
119
|
+
|
120
|
+
def failure_message_when_negated
|
121
|
+
"expected #{@actual.current_path.inspect} to be larger than #{@width} by #{@height}, but it wasn't."
|
122
|
+
end
|
123
|
+
|
124
|
+
def description
|
125
|
+
"be no larger than #{@width} by #{@height}"
|
126
|
+
end
|
127
|
+
|
128
|
+
# RSpec 2 compatibility:
|
129
|
+
alias_method :negative_failure_message, :failure_message_when_negated
|
130
|
+
end
|
131
|
+
|
132
|
+
def be_no_larger_than(width, height)
|
133
|
+
BeNoLargerThan.new(width, height)
|
134
|
+
end
|
135
|
+
|
136
|
+
class HaveDimensions # :nodoc:
|
137
|
+
def initialize(width, height)
|
138
|
+
@width, @height = width, height
|
139
|
+
end
|
140
|
+
|
141
|
+
def matches?(actual)
|
142
|
+
@actual = actual
|
143
|
+
# Satisfy expectation here. Return false or raise an error if it's not met.
|
144
|
+
image = ImageLoader.load_image(@actual.current_path)
|
145
|
+
@actual_width = image.width
|
146
|
+
@actual_height = image.height
|
147
|
+
@actual_width == @width && @actual_height == @height
|
148
|
+
end
|
149
|
+
|
150
|
+
def failure_message
|
151
|
+
"expected #{@actual.current_path.inspect} to have an exact size of #{@width} by #{@height}, but it was #{@actual_width} by #{@actual_height}."
|
152
|
+
end
|
153
|
+
|
154
|
+
def failure_message_when_negated
|
155
|
+
"expected #{@actual.current_path.inspect} not to have an exact size of #{@width} by #{@height}, but it did."
|
156
|
+
end
|
157
|
+
|
158
|
+
def description
|
159
|
+
"have an exact size of #{@width} by #{@height}"
|
160
|
+
end
|
161
|
+
|
162
|
+
# RSpec 2 compatibility:
|
163
|
+
alias_method :negative_failure_message, :failure_message_when_negated
|
164
|
+
end
|
165
|
+
|
166
|
+
def have_dimensions(width, height)
|
167
|
+
HaveDimensions.new(width, height)
|
168
|
+
end
|
169
|
+
|
170
|
+
class HaveHeight # :nodoc:
|
171
|
+
def initialize(height)
|
172
|
+
@height = height
|
173
|
+
end
|
174
|
+
|
175
|
+
def matches?(actual)
|
176
|
+
@actual = actual
|
177
|
+
# Satisfy expectation here. Return false or raise an error if it's not met.
|
178
|
+
image = ImageLoader.load_image(@actual.current_path)
|
179
|
+
@actual_height = image.height
|
180
|
+
@actual_height == @height
|
181
|
+
end
|
182
|
+
|
183
|
+
def failure_message
|
184
|
+
"expected #{@actual.current_path.inspect} to have an exact size of #{@height}, but it was #{@actual_height}."
|
185
|
+
end
|
186
|
+
|
187
|
+
def failure_message_when_negated
|
188
|
+
"expected #{@actual.current_path.inspect} not to have an exact size of #{@height}, but it did."
|
189
|
+
end
|
190
|
+
|
191
|
+
def description
|
192
|
+
"have an exact height of #{@height}"
|
193
|
+
end
|
194
|
+
|
195
|
+
# RSpec 2 compatibility:
|
196
|
+
alias_method :negative_failure_message, :failure_message_when_negated
|
197
|
+
end
|
198
|
+
|
199
|
+
def have_height(height)
|
200
|
+
HaveHeight.new(height)
|
201
|
+
end
|
202
|
+
|
203
|
+
class HaveWidth # :nodoc:
|
204
|
+
def initialize(width)
|
205
|
+
@width = width
|
206
|
+
end
|
207
|
+
|
208
|
+
def matches?(actual)
|
209
|
+
@actual = actual
|
210
|
+
# Satisfy expectation here. Return false or raise an error if it's not met.
|
211
|
+
image = ImageLoader.load_image(@actual.current_path)
|
212
|
+
@actual_width = image.width
|
213
|
+
@actual_width == @width
|
214
|
+
end
|
215
|
+
|
216
|
+
def failure_message
|
217
|
+
"expected #{@actual.current_path.inspect} to have an exact size of #{@width}, but it was #{@actual_width}."
|
218
|
+
end
|
219
|
+
|
220
|
+
def failure_message_when_negated
|
221
|
+
"expected #{@actual.current_path.inspect} not to have an exact size of #{@width}, but it did."
|
222
|
+
end
|
223
|
+
|
224
|
+
def description
|
225
|
+
"have an exact width of #{@width}"
|
226
|
+
end
|
227
|
+
|
228
|
+
# RSpec 2 compatibility:
|
229
|
+
alias_method :negative_failure_message, :failure_message_when_negated
|
230
|
+
end
|
231
|
+
|
232
|
+
def have_width(width)
|
233
|
+
HaveWidth.new(width)
|
234
|
+
end
|
235
|
+
|
236
|
+
class BeNoWiderThan # :nodoc:
|
237
|
+
def initialize(width)
|
238
|
+
@width = width
|
239
|
+
end
|
240
|
+
|
241
|
+
def matches?(actual)
|
242
|
+
@actual = actual
|
243
|
+
# Satisfy expectation here. Return false or raise an error if it's not met.
|
244
|
+
image = ImageLoader.load_image(@actual.current_path)
|
245
|
+
@actual_width = image.width
|
246
|
+
@actual_width <= @width
|
247
|
+
end
|
248
|
+
|
249
|
+
def failure_message
|
250
|
+
"expected #{@actual.current_path.inspect} to be no wider than #{@width}, but it was #{@actual_width}."
|
251
|
+
end
|
252
|
+
|
253
|
+
def failure_message_when_negated
|
254
|
+
"expected #{@actual.current_path.inspect} not to be wider than #{@width}, but it is."
|
255
|
+
end
|
256
|
+
|
257
|
+
def description
|
258
|
+
"have a width less than or equal to #{@width}"
|
259
|
+
end
|
260
|
+
|
261
|
+
# RSpec 2 compatibility:
|
262
|
+
alias_method :negative_failure_message, :failure_message_when_negated
|
263
|
+
end
|
264
|
+
|
265
|
+
def be_no_wider_than(width)
|
266
|
+
BeNoWiderThan.new(width)
|
267
|
+
end
|
268
|
+
|
269
|
+
class BeNoTallerThan # :nodoc:
|
270
|
+
def initialize(height)
|
271
|
+
@height = height
|
272
|
+
end
|
273
|
+
|
274
|
+
def matches?(actual)
|
275
|
+
@actual = actual
|
276
|
+
# Satisfy expectation here. Return false or raise an error if it's not met.
|
277
|
+
image = ImageLoader.load_image(@actual.current_path)
|
278
|
+
@actual_height = image.height
|
279
|
+
@actual_height <= @height
|
280
|
+
end
|
281
|
+
|
282
|
+
def failure_message
|
283
|
+
"expected #{@actual.current_path.inspect} to be no taller than #{@height}, but it was #{@actual_height}."
|
284
|
+
end
|
285
|
+
|
286
|
+
def failure_message_when_negated
|
287
|
+
"expected #{@actual.current_path.inspect} not to be taller than #{@height}, but it is."
|
288
|
+
end
|
289
|
+
|
290
|
+
def description
|
291
|
+
"have a height less than or equal to #{@height}"
|
292
|
+
end
|
293
|
+
|
294
|
+
# RSpec 2 compatibility:
|
295
|
+
alias_method :negative_failure_message, :failure_message_when_negated
|
296
|
+
end
|
297
|
+
|
298
|
+
def be_no_taller_than(height)
|
299
|
+
BeNoTallerThan.new(height)
|
300
|
+
end
|
301
|
+
|
302
|
+
class BeFormat # :nodoc:
|
303
|
+
def initialize(expected)
|
304
|
+
@expected = expected
|
305
|
+
end
|
306
|
+
|
307
|
+
def matches?(actual)
|
308
|
+
@actual = actual
|
309
|
+
# Satisfy expectation here. Return false or raise an error if it's not met.
|
310
|
+
image = ImageLoader.load_image(@actual.current_path)
|
311
|
+
@actual_expected = image.format
|
312
|
+
!@expected.nil? && @actual_expected.casecmp(@expected).zero?
|
313
|
+
end
|
314
|
+
|
315
|
+
def failure_message
|
316
|
+
"expected #{@actual.current_path.inspect} to have #{@expected} format, but it was #{@actual_expected}."
|
317
|
+
end
|
318
|
+
|
319
|
+
def failure_message_when_negated
|
320
|
+
"expected #{@actual.current_path.inspect} not to have #{@expected} format, but it did."
|
321
|
+
end
|
322
|
+
|
323
|
+
def description
|
324
|
+
"have #{@expected} format"
|
325
|
+
end
|
326
|
+
|
327
|
+
# RSpec 2 compatibility:
|
328
|
+
alias_method :negative_failure_message, :failure_message_when_negated
|
329
|
+
end
|
330
|
+
|
331
|
+
def be_format(expected)
|
332
|
+
BeFormat.new(expected)
|
333
|
+
end
|
334
|
+
|
335
|
+
class ImageLoader # :nodoc:
|
336
|
+
def self.load_image(filename)
|
337
|
+
if defined? ::MiniMagick
|
338
|
+
MiniMagickWrapper.new(filename)
|
339
|
+
else
|
340
|
+
unless defined? ::Magick
|
341
|
+
begin
|
342
|
+
require 'rmagick'
|
343
|
+
rescue LoadError
|
344
|
+
begin
|
345
|
+
require 'RMagick'
|
346
|
+
rescue LoadError
|
347
|
+
puts "WARNING: Failed to require rmagick, image processing may fail!"
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
MagickWrapper.new(filename)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
class MagickWrapper # :nodoc:
|
357
|
+
attr_reader :image
|
358
|
+
|
359
|
+
def width
|
360
|
+
image.columns
|
361
|
+
end
|
362
|
+
|
363
|
+
def height
|
364
|
+
image.rows
|
365
|
+
end
|
366
|
+
|
367
|
+
def format
|
368
|
+
image.format
|
369
|
+
end
|
370
|
+
|
371
|
+
def initialize(filename)
|
372
|
+
@image = ::Magick::Image.read(filename).first
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
class MiniMagickWrapper # :nodoc:
|
377
|
+
attr_reader :image
|
378
|
+
|
379
|
+
def width
|
380
|
+
image[:width]
|
381
|
+
end
|
382
|
+
|
383
|
+
def height
|
384
|
+
image[:height]
|
385
|
+
end
|
386
|
+
|
387
|
+
def format
|
388
|
+
image[:format]
|
389
|
+
end
|
390
|
+
|
391
|
+
def initialize(filename)
|
392
|
+
@image = ::MiniMagick::Image.open(filename)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
end # Matchers
|
397
|
+
end # Test
|
398
|
+
end # SalebotUploader
|
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module SalebotUploader
|
4
|
+
|
5
|
+
class FormNotMultipart < UploadError
|
6
|
+
def message
|
7
|
+
"You tried to assign a String or a Pathname to an uploader, for security reasons, this is not allowed.\n\n If this is a file upload, please check that your upload form is multipart encoded."
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class CacheCounter
|
12
|
+
@@counter = 0
|
13
|
+
|
14
|
+
def self.increment
|
15
|
+
@@counter += 1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Generates a unique cache id for use in the caching system
|
21
|
+
#
|
22
|
+
# === Returns
|
23
|
+
#
|
24
|
+
# [String] a cache id in the format TIMEINT-PID-COUNTER-RND
|
25
|
+
#
|
26
|
+
def self.generate_cache_id
|
27
|
+
[
|
28
|
+
Time.now.utc.to_i,
|
29
|
+
SecureRandom.random_number(1_000_000_000_000_000),
|
30
|
+
'%04d' % (SalebotUploader::CacheCounter.increment % 10_000),
|
31
|
+
'%04d' % SecureRandom.random_number(10_000)
|
32
|
+
].map(&:to_s).join('-')
|
33
|
+
end
|
34
|
+
|
35
|
+
module Uploader
|
36
|
+
module Cache
|
37
|
+
extend ActiveSupport::Concern
|
38
|
+
|
39
|
+
include SalebotUploader::Uploader::Callbacks
|
40
|
+
include SalebotUploader::Uploader::Configuration
|
41
|
+
|
42
|
+
included do
|
43
|
+
prepend Module.new {
|
44
|
+
def initialize(*)
|
45
|
+
super
|
46
|
+
@staged = false
|
47
|
+
end
|
48
|
+
}
|
49
|
+
attr_accessor :staged
|
50
|
+
end
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
|
54
|
+
##
|
55
|
+
# Removes cached files which are older than one day. You could call this method
|
56
|
+
# from a rake task to clean out old cached files.
|
57
|
+
#
|
58
|
+
# You can call this method directly on the module like this:
|
59
|
+
#
|
60
|
+
# SalebotUploader.clean_cached_files!
|
61
|
+
#
|
62
|
+
# === Note
|
63
|
+
#
|
64
|
+
# This only works as long as you haven't done anything funky with your cache_dir.
|
65
|
+
# It's recommended that you keep cache files in one place only.
|
66
|
+
#
|
67
|
+
def clean_cached_files!(seconds=60*60*24)
|
68
|
+
(cache_storage || storage).new(new).clean_cache!(seconds)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Returns true if the uploader has been cached
|
74
|
+
#
|
75
|
+
# === Returns
|
76
|
+
#
|
77
|
+
# [Bool] whether the current file is cached
|
78
|
+
#
|
79
|
+
def cached?
|
80
|
+
!!@cache_id
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Caches the remotely stored file
|
85
|
+
#
|
86
|
+
# This is useful when about to process images. Most processing solutions
|
87
|
+
# require the file to be stored on the local filesystem.
|
88
|
+
#
|
89
|
+
def cache_stored_file!
|
90
|
+
cache!
|
91
|
+
end
|
92
|
+
|
93
|
+
def sanitized_file
|
94
|
+
ActiveSupport::Deprecation.warn('#sanitized_file is deprecated, use #file instead.')
|
95
|
+
file
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Returns a String which uniquely identifies the currently cached file for later retrieval
|
100
|
+
#
|
101
|
+
# === Returns
|
102
|
+
#
|
103
|
+
# [String] a cache name, in the format TIMEINT-PID-COUNTER-RND/filename.txt
|
104
|
+
#
|
105
|
+
def cache_name
|
106
|
+
File.join(cache_id, original_filename) if cache_id && original_filename
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Caches the given file. Calls process! to trigger any process callbacks.
|
111
|
+
#
|
112
|
+
# By default, cache!() uses copy_to(), which operates by copying the file
|
113
|
+
# to the cache, then deleting the original file. If move_to_cache() is
|
114
|
+
# overridden to return true, then cache!() uses move_to(), which simply
|
115
|
+
# moves the file to the cache. Useful for large files.
|
116
|
+
#
|
117
|
+
# === Parameters
|
118
|
+
#
|
119
|
+
# [new_file (File, IOString, Tempfile)] any kind of file object
|
120
|
+
#
|
121
|
+
# === Raises
|
122
|
+
#
|
123
|
+
# [SalebotUploader::FormNotMultipart] if the assigned parameter is a string
|
124
|
+
#
|
125
|
+
def cache!(new_file = file)
|
126
|
+
new_file = SalebotUploader::SanitizedFile.new(new_file)
|
127
|
+
return if new_file.empty?
|
128
|
+
|
129
|
+
raise SalebotUploader::FormNotMultipart if new_file.is_path? && ensure_multipart_form
|
130
|
+
|
131
|
+
self.cache_id = SalebotUploader.generate_cache_id unless cache_id
|
132
|
+
|
133
|
+
@identifier = nil
|
134
|
+
@staged = true
|
135
|
+
@filename = new_file.filename
|
136
|
+
self.original_filename = new_file.filename
|
137
|
+
|
138
|
+
begin
|
139
|
+
# first, create a workfile on which we perform processings
|
140
|
+
if move_to_cache
|
141
|
+
@file = new_file.move_to(File.expand_path(workfile_path, root), permissions, directory_permissions)
|
142
|
+
else
|
143
|
+
@file = new_file.copy_to(File.expand_path(workfile_path, root), permissions, directory_permissions)
|
144
|
+
end
|
145
|
+
|
146
|
+
with_callbacks(:cache, @file) do
|
147
|
+
@file = cache_storage.cache!(@file)
|
148
|
+
end
|
149
|
+
ensure
|
150
|
+
FileUtils.rm_rf(workfile_path(''))
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Retrieves the file with the given cache_name from the cache.
|
156
|
+
#
|
157
|
+
# === Parameters
|
158
|
+
#
|
159
|
+
# [cache_name (String)] uniquely identifies a cache file
|
160
|
+
#
|
161
|
+
# === Raises
|
162
|
+
#
|
163
|
+
# [SalebotUploader::InvalidParameter] if the cache_name is incorrectly formatted.
|
164
|
+
#
|
165
|
+
def retrieve_from_cache!(cache_name)
|
166
|
+
with_callbacks(:retrieve_from_cache, cache_name) do
|
167
|
+
self.cache_id, self.original_filename = cache_name.to_s.split('/', 2)
|
168
|
+
@staged = true
|
169
|
+
@filename = original_filename
|
170
|
+
@file = cache_storage.retrieve_from_cache!(full_original_filename)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Calculates the path where the cache file should be stored.
|
176
|
+
#
|
177
|
+
# === Parameters
|
178
|
+
#
|
179
|
+
# [for_file (String)] name of the file <optional>
|
180
|
+
#
|
181
|
+
# === Returns
|
182
|
+
#
|
183
|
+
# [String] the cache path
|
184
|
+
#
|
185
|
+
def cache_path(for_file=full_original_filename)
|
186
|
+
File.join(*[cache_dir, @cache_id, for_file].compact)
|
187
|
+
end
|
188
|
+
|
189
|
+
protected
|
190
|
+
|
191
|
+
attr_reader :cache_id
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def workfile_path(for_file=original_filename)
|
196
|
+
File.join(SalebotUploader.tmp_path, @cache_id, version_name.to_s, for_file)
|
197
|
+
end
|
198
|
+
|
199
|
+
attr_reader :original_filename
|
200
|
+
|
201
|
+
def cache_id=(cache_id)
|
202
|
+
# Earlier version used 3 part cache_id. Thus we should allow for
|
203
|
+
# the cache_id to have both 3 part and 4 part formats.
|
204
|
+
raise SalebotUploader::InvalidParameter, "invalid cache id" unless cache_id =~ /\A(-)?[\d]+\-[\d]+(\-[\d]{4})?\-[\d]{4}\z/
|
205
|
+
@cache_id = cache_id
|
206
|
+
end
|
207
|
+
|
208
|
+
def original_filename=(filename)
|
209
|
+
raise SalebotUploader::InvalidParameter, "invalid filename" if filename =~ SalebotUploader::SanitizedFile.sanitize_regexp
|
210
|
+
@original_filename = filename
|
211
|
+
end
|
212
|
+
|
213
|
+
def cache_storage
|
214
|
+
@cache_storage ||= (self.class.cache_storage || self.class.storage).new(self)
|
215
|
+
end
|
216
|
+
|
217
|
+
# We can override the full_original_filename method in other modules
|
218
|
+
def full_original_filename
|
219
|
+
forcing_extension(original_filename)
|
220
|
+
end
|
221
|
+
end # Cache
|
222
|
+
end # Uploader
|
223
|
+
end # SalebotUploader
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module Callbacks
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
class_attribute :_before_callbacks, :_after_callbacks,
|
8
|
+
:instance_writer => false
|
9
|
+
self._before_callbacks = Hash.new []
|
10
|
+
self._after_callbacks = Hash.new []
|
11
|
+
end
|
12
|
+
|
13
|
+
def with_callbacks(kind, *args)
|
14
|
+
self.class._before_callbacks[kind].each { |c| send c, *args }
|
15
|
+
yield
|
16
|
+
self.class._after_callbacks[kind].each { |c| send c, *args }
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def before(kind, callback)
|
21
|
+
self._before_callbacks = self._before_callbacks.
|
22
|
+
merge kind => _before_callbacks[kind] + [callback]
|
23
|
+
end
|
24
|
+
|
25
|
+
def after(kind, callback)
|
26
|
+
self._after_callbacks = self._after_callbacks.
|
27
|
+
merge kind => _after_callbacks[kind] + [callback]
|
28
|
+
end
|
29
|
+
end # ClassMethods
|
30
|
+
|
31
|
+
end # Callbacks
|
32
|
+
end # Uploader
|
33
|
+
end # SalebotUploader
|