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,446 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
|
3
|
+
##
|
4
|
+
# If a Class is extended with this module, it gains the mount_uploader
|
5
|
+
# method, which is used for mapping attributes to uploaders and allowing
|
6
|
+
# easy assignment.
|
7
|
+
#
|
8
|
+
# You can use mount_uploader with pretty much any class, however it is
|
9
|
+
# intended to be used with some kind of persistent storage, like an ORM.
|
10
|
+
# If you want to persist the uploaded files in a particular Class, it
|
11
|
+
# needs to implement a `read_uploader` and a `write_uploader` method.
|
12
|
+
#
|
13
|
+
module Mount
|
14
|
+
|
15
|
+
##
|
16
|
+
# === Returns
|
17
|
+
#
|
18
|
+
# [Hash{Symbol => SalebotUploader}] what uploaders are mounted on which columns
|
19
|
+
#
|
20
|
+
def uploaders
|
21
|
+
@uploaders ||= superclass.respond_to?(:uploaders) ? superclass.uploaders.dup : {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def uploader_options
|
25
|
+
@uploader_options ||= superclass.respond_to?(:uploader_options) ? superclass.uploader_options.dup : {}
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Return a particular option for a particular uploader
|
30
|
+
#
|
31
|
+
# === Parameters
|
32
|
+
#
|
33
|
+
# [column (Symbol)] The column the uploader is mounted at
|
34
|
+
# [option (Symbol)] The option, e.g. validate_integrity
|
35
|
+
#
|
36
|
+
# === Returns
|
37
|
+
#
|
38
|
+
# [Object] The option value
|
39
|
+
#
|
40
|
+
def uploader_option(column, option)
|
41
|
+
if uploader_options[column].has_key?(option)
|
42
|
+
uploader_options[column][option]
|
43
|
+
else
|
44
|
+
uploaders[column].send(option)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Mounts the given uploader on the given column. This means that assigning
|
50
|
+
# and reading from the column will upload and retrieve files. Supposing
|
51
|
+
# that a User class has an uploader mounted on image, you can assign and
|
52
|
+
# retrieve files like this:
|
53
|
+
#
|
54
|
+
# @user.image # => <Uploader>
|
55
|
+
# @user.image.store!(some_file_object)
|
56
|
+
#
|
57
|
+
# @user.image.url # => '/some_url.png'
|
58
|
+
#
|
59
|
+
# It is also possible (but not recommended) to omit the uploader, which
|
60
|
+
# will create an anonymous uploader class.
|
61
|
+
#
|
62
|
+
# Passing a block makes it possible to customize the uploader. This can be
|
63
|
+
# convenient for brevity, but if there is any significant logic in the
|
64
|
+
# uploader, you should do the right thing and have it in its own file.
|
65
|
+
#
|
66
|
+
# === Added instance methods
|
67
|
+
#
|
68
|
+
# Supposing a class has used +mount_uploader+ to mount an uploader on a column
|
69
|
+
# named +image+, in that case the following methods will be added to the class:
|
70
|
+
#
|
71
|
+
# [image] Returns an instance of the uploader only if anything has been uploaded
|
72
|
+
# [image=] Caches the given file
|
73
|
+
#
|
74
|
+
# [image_url] Returns the url to the uploaded file
|
75
|
+
#
|
76
|
+
# [image_cache] Returns a string that identifies the cache location of the file
|
77
|
+
# [image_cache=] Retrieves the file from the cache based on the given cache name
|
78
|
+
#
|
79
|
+
# [remote_image_url] Returns previously cached remote url
|
80
|
+
# [remote_image_url=] Retrieve the file from the remote url
|
81
|
+
#
|
82
|
+
# [remove_image] An attribute reader that can be used with a checkbox to mark a file for removal
|
83
|
+
# [remove_image=] An attribute writer that can be used with a checkbox to mark a file for removal
|
84
|
+
# [remove_image?] Whether the file should be removed when store_image! is called.
|
85
|
+
#
|
86
|
+
# [store_image!] Stores a file that has been assigned with +image=+
|
87
|
+
# [remove_image!] Removes the uploaded file from the filesystem.
|
88
|
+
#
|
89
|
+
# [image_integrity_error] Returns an error object if the last file to be assigned caused an integrity error
|
90
|
+
# [image_processing_error] Returns an error object if the last file to be assigned caused a processing error
|
91
|
+
# [image_download_error] Returns an error object if the last file to be remotely assigned caused a download error
|
92
|
+
#
|
93
|
+
# [image_identifier] Reads out the identifier of the file
|
94
|
+
#
|
95
|
+
# === Parameters
|
96
|
+
#
|
97
|
+
# [column (Symbol)] the attribute to mount this uploader on
|
98
|
+
# [uploader (SalebotUploader::Uploader)] the uploader class to mount
|
99
|
+
# [options (Hash{Symbol => Object})] a set of options
|
100
|
+
# [&block (Proc)] customize anonymous uploaders
|
101
|
+
#
|
102
|
+
# === Options
|
103
|
+
#
|
104
|
+
# [:mount_on => Symbol] if the name of the column to be serialized to differs you can override it using this option
|
105
|
+
# [:ignore_integrity_errors => Boolean] if set to true, integrity errors will result in caching failing silently
|
106
|
+
# [:ignore_processing_errors => Boolean] if set to true, processing errors will result in caching failing silently
|
107
|
+
#
|
108
|
+
# === Examples
|
109
|
+
#
|
110
|
+
# Mounting uploaders on different columns.
|
111
|
+
#
|
112
|
+
# class Song
|
113
|
+
# mount_uploader :lyrics, LyricsUploader
|
114
|
+
# mount_uploader :alternative_lyrics, LyricsUploader
|
115
|
+
# mount_uploader :file, SongUploader
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# This will add an anonymous uploader with only the default settings:
|
119
|
+
#
|
120
|
+
# class Data
|
121
|
+
# mount_uploader :csv
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# this will add an anonymous uploader overriding the store_dir:
|
125
|
+
#
|
126
|
+
# class Product
|
127
|
+
# mount_uploader :blueprint do
|
128
|
+
# def store_dir
|
129
|
+
# 'blueprints'
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
def mount_uploader(column, uploader=nil, options={}, &block)
|
135
|
+
mount_base(column, uploader, options.merge(multiple: false), &block)
|
136
|
+
|
137
|
+
mod = Module.new
|
138
|
+
include mod
|
139
|
+
mod.class_eval <<-RUBY, __FILE__, __LINE__+1
|
140
|
+
|
141
|
+
def #{column}
|
142
|
+
_mounter(:#{column}).uploaders[0] ||= _mounter(:#{column}).blank_uploader
|
143
|
+
end
|
144
|
+
|
145
|
+
def #{column}=(new_file)
|
146
|
+
_mounter(:#{column}).cache([new_file])
|
147
|
+
end
|
148
|
+
|
149
|
+
def #{column}_url(*args)
|
150
|
+
#{column}.url(*args)
|
151
|
+
end
|
152
|
+
|
153
|
+
def #{column}_cache
|
154
|
+
_mounter(:#{column}).cache_names[0]
|
155
|
+
end
|
156
|
+
|
157
|
+
def #{column}_cache=(cache_name)
|
158
|
+
_mounter(:#{column}).cache_names = [cache_name]
|
159
|
+
end
|
160
|
+
|
161
|
+
def remote_#{column}_url
|
162
|
+
[_mounter(:#{column}).remote_urls].flatten[0]
|
163
|
+
end
|
164
|
+
|
165
|
+
def remote_#{column}_url=(url)
|
166
|
+
_mounter(:#{column}).remote_urls = url
|
167
|
+
end
|
168
|
+
|
169
|
+
def remote_#{column}_request_header=(header)
|
170
|
+
_mounter(:#{column}).remote_request_headers = [header]
|
171
|
+
end
|
172
|
+
|
173
|
+
def #{column}_identifier
|
174
|
+
_mounter(:#{column}).read_identifiers[0]
|
175
|
+
end
|
176
|
+
|
177
|
+
def #{column}_integrity_error
|
178
|
+
#{column}_integrity_errors.last
|
179
|
+
end
|
180
|
+
|
181
|
+
def #{column}_processing_error
|
182
|
+
#{column}_processing_errors.last
|
183
|
+
end
|
184
|
+
|
185
|
+
def #{column}_download_error
|
186
|
+
#{column}_download_errors.last
|
187
|
+
end
|
188
|
+
RUBY
|
189
|
+
end
|
190
|
+
|
191
|
+
##
|
192
|
+
# Mounts the given uploader on the given array column. This means that
|
193
|
+
# assigning and reading from the array column will upload and retrieve
|
194
|
+
# multiple files. Supposing that a User class has an uploader mounted on
|
195
|
+
# images, and that images can store an array, for example it could be a
|
196
|
+
# PostgreSQL JSON column. You can assign and retrieve files like this:
|
197
|
+
#
|
198
|
+
# @user.images # => []
|
199
|
+
# @user.images = [some_file_object]
|
200
|
+
# @user.images # => [<Uploader>]
|
201
|
+
#
|
202
|
+
# @user.images[0].url # => '/some_url.png'
|
203
|
+
#
|
204
|
+
# It is also possible (but not recommended) to omit the uploader, which
|
205
|
+
# will create an anonymous uploader class.
|
206
|
+
#
|
207
|
+
# Passing a block makes it possible to customize the uploader. This can be
|
208
|
+
# convenient for brevity, but if there is any significant logic in the
|
209
|
+
# uploader, you should do the right thing and have it in its own file.
|
210
|
+
#
|
211
|
+
# === Added instance methods
|
212
|
+
#
|
213
|
+
# Supposing a class has used +mount_uploaders+ to mount an uploader on a column
|
214
|
+
# named +images+, in that case the following methods will be added to the class:
|
215
|
+
#
|
216
|
+
# [images] Returns an array of uploaders for each uploaded file
|
217
|
+
# [images=] Caches the given files
|
218
|
+
#
|
219
|
+
# [images_urls] Returns the urls to the uploaded files
|
220
|
+
#
|
221
|
+
# [images_cache] Returns a string that identifies the cache location of the files
|
222
|
+
# [images_cache=] Retrieves the files from the cache based on the given cache name
|
223
|
+
#
|
224
|
+
# [remote_image_urls] Returns previously cached remote urls
|
225
|
+
# [remote_image_urls=] Retrieve files from the given remote urls
|
226
|
+
#
|
227
|
+
# [remove_images] An attribute reader that can be used with a checkbox to mark the files for removal
|
228
|
+
# [remove_images=] An attribute writer that can be used with a checkbox to mark the files for removal
|
229
|
+
# [remove_images?] Whether the files should be removed when store_image! is called.
|
230
|
+
#
|
231
|
+
# [store_images!] Stores all files that have been assigned with +images=+
|
232
|
+
# [remove_images!] Removes the uploaded file from the filesystem.
|
233
|
+
#
|
234
|
+
# [image_integrity_errors] Returns error objects of files which failed to pass integrity check
|
235
|
+
# [image_processing_errors] Returns error objects of files which failed to be processed
|
236
|
+
# [image_download_errors] Returns error objects of files which failed to be downloaded
|
237
|
+
#
|
238
|
+
# [image_identifiers] Reads out the identifiers of the files
|
239
|
+
#
|
240
|
+
# === Parameters
|
241
|
+
#
|
242
|
+
# [column (Symbol)] the attribute to mount this uploader on
|
243
|
+
# [uploader (SalebotUploader::Uploader)] the uploader class to mount
|
244
|
+
# [options (Hash{Symbol => Object})] a set of options
|
245
|
+
# [&block (Proc)] customize anonymous uploaders
|
246
|
+
#
|
247
|
+
# === Options
|
248
|
+
#
|
249
|
+
# [:mount_on => Symbol] if the name of the column to be serialized to differs you can override it using this option
|
250
|
+
# [:ignore_integrity_errors => Boolean] if set to true, integrity errors will result in caching failing silently
|
251
|
+
# [:ignore_processing_errors => Boolean] if set to true, processing errors will result in caching failing silently
|
252
|
+
#
|
253
|
+
# === Examples
|
254
|
+
#
|
255
|
+
# Mounting uploaders on different columns.
|
256
|
+
#
|
257
|
+
# class Song
|
258
|
+
# mount_uploaders :lyrics, LyricsUploader
|
259
|
+
# mount_uploaders :alternative_lyrics, LyricsUploader
|
260
|
+
# mount_uploaders :files, SongUploader
|
261
|
+
# end
|
262
|
+
#
|
263
|
+
# This will add an anonymous uploader with only the default settings:
|
264
|
+
#
|
265
|
+
# class Data
|
266
|
+
# mount_uploaders :csv_files
|
267
|
+
# end
|
268
|
+
#
|
269
|
+
# this will add an anonymous uploader overriding the store_dir:
|
270
|
+
#
|
271
|
+
# class Product
|
272
|
+
# mount_uploaders :blueprints do
|
273
|
+
# def store_dir
|
274
|
+
# 'blueprints'
|
275
|
+
# end
|
276
|
+
# end
|
277
|
+
# end
|
278
|
+
#
|
279
|
+
def mount_uploaders(column, uploader=nil, options={}, &block)
|
280
|
+
mount_base(column, uploader, options.merge(multiple: true), &block)
|
281
|
+
|
282
|
+
mod = Module.new
|
283
|
+
include mod
|
284
|
+
mod.class_eval <<-RUBY, __FILE__, __LINE__+1
|
285
|
+
|
286
|
+
def #{column}
|
287
|
+
_mounter(:#{column}).uploaders
|
288
|
+
end
|
289
|
+
|
290
|
+
def #{column}=(new_files)
|
291
|
+
_mounter(:#{column}).cache(new_files)
|
292
|
+
end
|
293
|
+
|
294
|
+
def #{column}_urls(*args)
|
295
|
+
_mounter(:#{column}).urls(*args)
|
296
|
+
end
|
297
|
+
|
298
|
+
def #{column}_cache
|
299
|
+
names = _mounter(:#{column}).cache_names
|
300
|
+
names.to_json if names.present?
|
301
|
+
end
|
302
|
+
|
303
|
+
def #{column}_cache=(cache_name)
|
304
|
+
_mounter(:#{column}).cache_names = JSON.parse(cache_name) if cache_name.present?
|
305
|
+
end
|
306
|
+
|
307
|
+
def remote_#{column}_urls
|
308
|
+
_mounter(:#{column}).remote_urls
|
309
|
+
end
|
310
|
+
|
311
|
+
def remote_#{column}_urls=(urls)
|
312
|
+
_mounter(:#{column}).remote_urls = urls
|
313
|
+
end
|
314
|
+
|
315
|
+
def remote_#{column}_request_headers=(headers)
|
316
|
+
_mounter(:#{column}).remote_request_headers = headers
|
317
|
+
end
|
318
|
+
|
319
|
+
def #{column}_identifiers
|
320
|
+
_mounter(:#{column}).read_identifiers
|
321
|
+
end
|
322
|
+
RUBY
|
323
|
+
end
|
324
|
+
|
325
|
+
private
|
326
|
+
|
327
|
+
def mount_base(column, uploader=nil, options={}, &block)
|
328
|
+
include SalebotUploader::Mount::Extension
|
329
|
+
|
330
|
+
uploader = build_uploader(uploader, column, &block)
|
331
|
+
uploaders[column.to_sym] = uploader
|
332
|
+
uploader_options[column.to_sym] = options
|
333
|
+
|
334
|
+
# Make sure to write over accessors directly defined on the class.
|
335
|
+
# Simply super to the included module below.
|
336
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
337
|
+
def #{column}; super; end
|
338
|
+
def #{column}=(new_file); super; end
|
339
|
+
RUBY
|
340
|
+
|
341
|
+
mod = Module.new
|
342
|
+
include mod
|
343
|
+
mod.class_eval <<-RUBY, __FILE__, __LINE__+1
|
344
|
+
|
345
|
+
def #{column}?
|
346
|
+
_mounter(:#{column}).present?
|
347
|
+
end
|
348
|
+
|
349
|
+
def remove_#{column}
|
350
|
+
_mounter(:#{column}).remove
|
351
|
+
end
|
352
|
+
|
353
|
+
def remove_#{column}!
|
354
|
+
_mounter(:#{column}).remove!
|
355
|
+
self.remove_#{column} = true
|
356
|
+
write_#{column}_identifier
|
357
|
+
end
|
358
|
+
|
359
|
+
def remove_#{column}=(value)
|
360
|
+
_mounter(:#{column}).remove = value
|
361
|
+
end
|
362
|
+
|
363
|
+
def remove_#{column}?
|
364
|
+
_mounter(:#{column}).remove?
|
365
|
+
end
|
366
|
+
|
367
|
+
def store_#{column}!
|
368
|
+
_mounter(:#{column}).store!
|
369
|
+
end
|
370
|
+
|
371
|
+
def #{column}_integrity_errors
|
372
|
+
_mounter(:#{column}).integrity_errors
|
373
|
+
end
|
374
|
+
|
375
|
+
def #{column}_processing_errors
|
376
|
+
_mounter(:#{column}).processing_errors
|
377
|
+
end
|
378
|
+
|
379
|
+
def #{column}_download_errors
|
380
|
+
_mounter(:#{column}).download_errors
|
381
|
+
end
|
382
|
+
|
383
|
+
def write_#{column}_identifier
|
384
|
+
_mounter(:#{column}).write_identifier
|
385
|
+
end
|
386
|
+
|
387
|
+
def mark_remove_#{column}_false
|
388
|
+
_mounter(:#{column}).remove = false
|
389
|
+
end
|
390
|
+
|
391
|
+
def reset_previous_changes_for_#{column}
|
392
|
+
_mounter(:#{column}).reset_changes!
|
393
|
+
end
|
394
|
+
|
395
|
+
def remove_previously_stored_#{column}
|
396
|
+
_mounter(:#{column}).remove_previous
|
397
|
+
end
|
398
|
+
|
399
|
+
def remove_rolled_back_#{column}
|
400
|
+
_mounter(:#{column}).remove_added
|
401
|
+
end
|
402
|
+
RUBY
|
403
|
+
end
|
404
|
+
|
405
|
+
def build_uploader(uploader, column, &block)
|
406
|
+
uploader ||= SalebotUploader::Uploader::Base
|
407
|
+
return uploader unless block_given?
|
408
|
+
|
409
|
+
uploader = Class.new(uploader)
|
410
|
+
const_set("SalebotUploader#{column.to_s.camelize}Uploader", uploader)
|
411
|
+
|
412
|
+
uploader.class_eval(&block)
|
413
|
+
|
414
|
+
uploader
|
415
|
+
end
|
416
|
+
|
417
|
+
module Extension
|
418
|
+
|
419
|
+
##
|
420
|
+
# overwrite this to read from a serialized attribute
|
421
|
+
#
|
422
|
+
def read_uploader(column); end
|
423
|
+
|
424
|
+
##
|
425
|
+
# overwrite this to write to a serialized attribute
|
426
|
+
#
|
427
|
+
def write_uploader(column, identifier); end
|
428
|
+
|
429
|
+
private
|
430
|
+
|
431
|
+
def initialize_dup(other)
|
432
|
+
@_mounters = @_mounters.dup
|
433
|
+
super
|
434
|
+
end
|
435
|
+
|
436
|
+
def _mounter(column)
|
437
|
+
# We cannot memoize in frozen objects :(
|
438
|
+
return Mounter.build(self, column) if frozen?
|
439
|
+
@_mounters ||= {}
|
440
|
+
@_mounters[column] ||= Mounter.build(self, column)
|
441
|
+
end
|
442
|
+
|
443
|
+
end # Extension
|
444
|
+
|
445
|
+
end # Mount
|
446
|
+
end # SalebotUploader
|
@@ -0,0 +1,255 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
|
3
|
+
# this is an internal class, used by SalebotUploader::Mount so that
|
4
|
+
# we don't pollute the model with a lot of methods.
|
5
|
+
class Mounter # :nodoc:
|
6
|
+
class Single < Mounter # :nodoc
|
7
|
+
def identifier
|
8
|
+
uploaders.first&.identifier
|
9
|
+
end
|
10
|
+
|
11
|
+
def temporary_identifier
|
12
|
+
temporary_identifiers.first
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Multiple < Mounter # :nodoc
|
17
|
+
def identifier
|
18
|
+
uploaders.map(&:identifier).presence
|
19
|
+
end
|
20
|
+
|
21
|
+
def temporary_identifier
|
22
|
+
temporary_identifiers.presence
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.build(record, column)
|
27
|
+
if record.class.uploader_options[column][:multiple]
|
28
|
+
Multiple.new(record, column)
|
29
|
+
else
|
30
|
+
Single.new(record, column)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :column, :record, :remote_urls, :remove,
|
35
|
+
:integrity_errors, :processing_errors, :download_errors
|
36
|
+
attr_accessor :remote_request_headers, :uploader_options
|
37
|
+
|
38
|
+
def initialize(record, column)
|
39
|
+
@record = record
|
40
|
+
@column = column
|
41
|
+
@options = record.class.uploader_options[column]
|
42
|
+
@download_errors = []
|
43
|
+
@processing_errors = []
|
44
|
+
@integrity_errors = []
|
45
|
+
|
46
|
+
@removed_uploaders = []
|
47
|
+
@added_uploaders = []
|
48
|
+
end
|
49
|
+
|
50
|
+
def uploader_class
|
51
|
+
record.class.uploaders[column]
|
52
|
+
end
|
53
|
+
|
54
|
+
def blank_uploader
|
55
|
+
uploader_class.new(record, column)
|
56
|
+
end
|
57
|
+
|
58
|
+
def identifiers
|
59
|
+
uploaders.map(&:identifier)
|
60
|
+
end
|
61
|
+
|
62
|
+
def read_identifiers
|
63
|
+
[record.read_uploader(serialization_column)].flatten.reject(&:blank?)
|
64
|
+
end
|
65
|
+
|
66
|
+
def uploaders
|
67
|
+
@uploaders ||= read_identifiers.map do |identifier|
|
68
|
+
uploader = blank_uploader
|
69
|
+
uploader.retrieve_from_store!(identifier)
|
70
|
+
uploader
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def cache(new_files)
|
75
|
+
return if !new_files.is_a?(Array) && new_files.blank?
|
76
|
+
old_uploaders = uploaders
|
77
|
+
@uploaders = new_files.map do |new_file|
|
78
|
+
handle_error do
|
79
|
+
if new_file.is_a?(String)
|
80
|
+
if (uploader = old_uploaders.detect { |old_uploader| old_uploader.identifier == new_file })
|
81
|
+
uploader.staged = true
|
82
|
+
uploader
|
83
|
+
else
|
84
|
+
begin
|
85
|
+
uploader = blank_uploader
|
86
|
+
uploader.retrieve_from_cache!(new_file)
|
87
|
+
uploader
|
88
|
+
rescue SalebotUploader::InvalidParameter
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
else
|
93
|
+
uploader = blank_uploader
|
94
|
+
uploader.cache!(new_file)
|
95
|
+
uploader
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end.reject(&:blank?)
|
99
|
+
@removed_uploaders += (old_uploaders - @uploaders)
|
100
|
+
write_temporary_identifier
|
101
|
+
end
|
102
|
+
|
103
|
+
def cache_names
|
104
|
+
uploaders.map(&:cache_name).compact
|
105
|
+
end
|
106
|
+
|
107
|
+
def cache_names=(cache_names)
|
108
|
+
cache_names = cache_names.reject(&:blank?)
|
109
|
+
return if cache_names.blank?
|
110
|
+
clear_unstaged
|
111
|
+
cache_names.each do |cache_name|
|
112
|
+
uploader = blank_uploader
|
113
|
+
uploader.retrieve_from_cache!(cache_name)
|
114
|
+
@uploaders << uploader
|
115
|
+
rescue SalebotUploader::InvalidParameter
|
116
|
+
# ignore
|
117
|
+
end
|
118
|
+
write_temporary_identifier
|
119
|
+
end
|
120
|
+
|
121
|
+
def remote_urls=(urls)
|
122
|
+
if urls.nil?
|
123
|
+
urls = []
|
124
|
+
else
|
125
|
+
urls = Array.wrap(urls).reject(&:blank?)
|
126
|
+
return if urls.blank?
|
127
|
+
end
|
128
|
+
@remote_urls = urls
|
129
|
+
|
130
|
+
clear_unstaged
|
131
|
+
@remote_urls.zip(remote_request_headers || []) do |url, header|
|
132
|
+
handle_error do
|
133
|
+
uploader = blank_uploader
|
134
|
+
uploader.download!(url, header || {})
|
135
|
+
@uploaders << uploader
|
136
|
+
end
|
137
|
+
end
|
138
|
+
write_temporary_identifier
|
139
|
+
end
|
140
|
+
|
141
|
+
def store!
|
142
|
+
uploaders.each(&:store!)
|
143
|
+
end
|
144
|
+
|
145
|
+
def write_identifier
|
146
|
+
return if record.frozen?
|
147
|
+
|
148
|
+
clear! if remove?
|
149
|
+
|
150
|
+
additions, remains = uploaders.partition(&:cached?)
|
151
|
+
existing_identifiers = (@removed_uploaders + remains).map(&:identifier)
|
152
|
+
additions.each do |uploader|
|
153
|
+
uploader.deduplicate(existing_identifiers)
|
154
|
+
existing_identifiers << uploader.identifier
|
155
|
+
end
|
156
|
+
@added_uploaders += additions
|
157
|
+
|
158
|
+
record.write_uploader(serialization_column, identifier)
|
159
|
+
end
|
160
|
+
|
161
|
+
def urls(*args)
|
162
|
+
uploaders.map { |u| u.url(*args) }
|
163
|
+
end
|
164
|
+
|
165
|
+
def blank?
|
166
|
+
uploaders.none?(&:present?)
|
167
|
+
end
|
168
|
+
|
169
|
+
def remove=(value)
|
170
|
+
@remove = value
|
171
|
+
write_temporary_identifier
|
172
|
+
end
|
173
|
+
|
174
|
+
def remove?
|
175
|
+
remove.present? && (remove.to_s !~ /\A0|false$\z/)
|
176
|
+
end
|
177
|
+
|
178
|
+
def remove!
|
179
|
+
uploaders.each(&:remove!)
|
180
|
+
clear!
|
181
|
+
end
|
182
|
+
|
183
|
+
def clear!
|
184
|
+
@removed_uploaders += uploaders
|
185
|
+
@remove = nil
|
186
|
+
@uploaders = []
|
187
|
+
end
|
188
|
+
|
189
|
+
def reset_changes!
|
190
|
+
@removed_uploaders = []
|
191
|
+
@added_uploaders = []
|
192
|
+
end
|
193
|
+
|
194
|
+
def serialization_column
|
195
|
+
option(:mount_on) || column
|
196
|
+
end
|
197
|
+
|
198
|
+
def remove_previous
|
199
|
+
current_paths = uploaders.map(&:path)
|
200
|
+
@removed_uploaders
|
201
|
+
.reject {|uploader| current_paths.include?(uploader.path) }
|
202
|
+
.each { |uploader| uploader.remove! if uploader.remove_previously_stored_files_after_update }
|
203
|
+
reset_changes!
|
204
|
+
end
|
205
|
+
|
206
|
+
def remove_added
|
207
|
+
current_paths = (@removed_uploaders + uploaders.select(&:staged)).map(&:path)
|
208
|
+
@added_uploaders
|
209
|
+
.reject {|uploader| current_paths.include?(uploader.path) }
|
210
|
+
.each { |uploader| uploader.remove! }
|
211
|
+
reset_changes!
|
212
|
+
end
|
213
|
+
|
214
|
+
private
|
215
|
+
|
216
|
+
def option(name)
|
217
|
+
self.uploader_options ||= {}
|
218
|
+
self.uploader_options[name] ||= record.class.uploader_option(column, name)
|
219
|
+
end
|
220
|
+
|
221
|
+
def clear_unstaged
|
222
|
+
@uploaders ||= []
|
223
|
+
staged, unstaged = @uploaders.partition(&:staged)
|
224
|
+
@uploaders = staged
|
225
|
+
@removed_uploaders += unstaged
|
226
|
+
end
|
227
|
+
|
228
|
+
def handle_error
|
229
|
+
yield
|
230
|
+
rescue SalebotUploader::DownloadError => e
|
231
|
+
@download_errors << e
|
232
|
+
raise e unless option(:ignore_download_errors)
|
233
|
+
rescue SalebotUploader::ProcessingError => e
|
234
|
+
@processing_errors << e
|
235
|
+
raise e unless option(:ignore_processing_errors)
|
236
|
+
rescue SalebotUploader::IntegrityError => e
|
237
|
+
@integrity_errors << e
|
238
|
+
raise e unless option(:ignore_integrity_errors)
|
239
|
+
end
|
240
|
+
|
241
|
+
def write_temporary_identifier
|
242
|
+
return if record.frozen?
|
243
|
+
|
244
|
+
record.write_uploader(serialization_column, temporary_identifier)
|
245
|
+
end
|
246
|
+
|
247
|
+
def temporary_identifiers
|
248
|
+
if remove?
|
249
|
+
[]
|
250
|
+
else
|
251
|
+
uploaders.map { |uploader| uploader.temporary_identifier }
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end # Mounter
|
255
|
+
end # SalebotUploader
|