salebot_uploader 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|