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,125 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module Processing
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
include SalebotUploader::Uploader::Callbacks
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :processors, :instance_writer => false
|
10
|
+
self.processors = []
|
11
|
+
|
12
|
+
before :cache, :process!
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
##
|
18
|
+
# Adds a processor callback which applies operations as a file is uploaded.
|
19
|
+
# The argument may be the name of any method of the uploader, expressed as a symbol,
|
20
|
+
# or a list of such methods, or a hash where the key is a method and the value is
|
21
|
+
# an array of arguments to call the method with. Also accepts an :if or :unless condition
|
22
|
+
#
|
23
|
+
# === Parameters
|
24
|
+
#
|
25
|
+
# args (*Symbol, Hash{Symbol => Array[]})
|
26
|
+
#
|
27
|
+
# === Examples
|
28
|
+
#
|
29
|
+
# class MyUploader < SalebotUploader::Uploader::Base
|
30
|
+
#
|
31
|
+
# process :sepiatone, :vignette
|
32
|
+
# process :scale => [200, 200]
|
33
|
+
# process :scale => [200, 200], :if => :image?
|
34
|
+
# process :scale => [200, 200], :unless => :disallowed_image_type?
|
35
|
+
# process :sepiatone, :if => :image?
|
36
|
+
#
|
37
|
+
# def sepiatone
|
38
|
+
# ...
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# def vignette
|
42
|
+
# ...
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# def scale(height, width)
|
46
|
+
# ...
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# def image?
|
50
|
+
# ...
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# def disallowed_image_type?
|
54
|
+
# ...
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
def process(*args)
|
60
|
+
new_processors = args.inject({}) do |hash, arg|
|
61
|
+
arg = { arg => [] } unless arg.is_a?(Hash)
|
62
|
+
hash.merge!(arg)
|
63
|
+
end
|
64
|
+
|
65
|
+
condition_type = new_processors.keys.detect { |key| [:if, :unless].include?(key) }
|
66
|
+
condition = new_processors.delete(:if) || new_processors.delete(:unless)
|
67
|
+
new_processors.each do |processor, processor_args|
|
68
|
+
self.processors += [[processor, processor_args, condition, condition_type]]
|
69
|
+
|
70
|
+
if processor == :convert
|
71
|
+
# Treat :convert specially, since it should trigger the file extension change
|
72
|
+
force_extension processor_args
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end # ClassMethods
|
77
|
+
|
78
|
+
##
|
79
|
+
# Apply all process callbacks added through SalebotUploader.process
|
80
|
+
#
|
81
|
+
def process!(new_file=nil)
|
82
|
+
return unless enable_processing
|
83
|
+
|
84
|
+
with_callbacks(:process, new_file) do
|
85
|
+
self.class.processors.each do |method, args, condition, condition_type|
|
86
|
+
if condition && condition_type == :if
|
87
|
+
if condition.respond_to?(:call)
|
88
|
+
next unless condition.call(self, :args => args, :method => method, :file => new_file)
|
89
|
+
else
|
90
|
+
next unless self.send(condition, new_file)
|
91
|
+
end
|
92
|
+
elsif condition && condition_type == :unless
|
93
|
+
if condition.respond_to?(:call)
|
94
|
+
next if condition.call(self, :args => args, :method => method, :file => new_file)
|
95
|
+
elsif self.send(condition, new_file)
|
96
|
+
next
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if args.is_a? Array
|
101
|
+
kwargs, args = args.partition { |arg| arg.is_a? Hash }
|
102
|
+
end
|
103
|
+
|
104
|
+
if kwargs.present?
|
105
|
+
kwargs = kwargs.reduce(:merge)
|
106
|
+
self.send(method, *args, **kwargs)
|
107
|
+
else
|
108
|
+
self.send(method, *args)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def forcing_extension(filename)
|
117
|
+
if force_extension && filename
|
118
|
+
Pathname.new(filename).sub_ext(".#{force_extension.to_s.delete_prefix('.')}").to_s
|
119
|
+
else
|
120
|
+
filename
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end # Processing
|
124
|
+
end # Uploader
|
125
|
+
end # SalebotUploader
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module Proxy
|
4
|
+
|
5
|
+
##
|
6
|
+
# === Returns
|
7
|
+
#
|
8
|
+
# [Boolean] Whether the uploaded file is blank
|
9
|
+
#
|
10
|
+
def blank?
|
11
|
+
file.blank?
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# === Returns
|
16
|
+
#
|
17
|
+
# [String] the path where the file is currently located.
|
18
|
+
#
|
19
|
+
def current_path
|
20
|
+
file.try(:path)
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :path, :current_path
|
24
|
+
|
25
|
+
##
|
26
|
+
# Returns a string that uniquely identifies the retrieved or last stored file
|
27
|
+
#
|
28
|
+
# === Returns
|
29
|
+
#
|
30
|
+
# [String] uniquely identifies a file
|
31
|
+
#
|
32
|
+
def identifier
|
33
|
+
@identifier || (file && storage.try(:identifier))
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Returns a String which is to be used as a temporary value which gets assigned to the column.
|
38
|
+
# The purpose is to mark the column that it will be updated. Finally before the save it will be
|
39
|
+
# overwritten by the #identifier value, which is usually #filename.
|
40
|
+
#
|
41
|
+
# === Returns
|
42
|
+
#
|
43
|
+
# [String] a temporary_identifier, by default @original_filename is used
|
44
|
+
#
|
45
|
+
def temporary_identifier
|
46
|
+
@original_filename || @identifier
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Read the contents of the file
|
51
|
+
#
|
52
|
+
# === Returns
|
53
|
+
#
|
54
|
+
# [String] contents of the file
|
55
|
+
#
|
56
|
+
def read(*args)
|
57
|
+
file.try(:read, *args)
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Fetches the size of the currently stored/cached file
|
62
|
+
#
|
63
|
+
# === Returns
|
64
|
+
#
|
65
|
+
# [Integer] size of the file
|
66
|
+
#
|
67
|
+
def size
|
68
|
+
file.try(:size) || 0
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Return the size of the file when asked for its length
|
73
|
+
#
|
74
|
+
# === Returns
|
75
|
+
#
|
76
|
+
# [Integer] size of the file
|
77
|
+
#
|
78
|
+
# === Note
|
79
|
+
#
|
80
|
+
# This was added because of the way Rails handles length/size validations in 3.0.6 and above.
|
81
|
+
#
|
82
|
+
def length
|
83
|
+
size
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Read the content type of the file
|
88
|
+
#
|
89
|
+
# === Returns
|
90
|
+
#
|
91
|
+
# [String] content type of the file
|
92
|
+
#
|
93
|
+
def content_type
|
94
|
+
file.try(:content_type)
|
95
|
+
end
|
96
|
+
|
97
|
+
end # Proxy
|
98
|
+
end # Uploader
|
99
|
+
end # SalebotUploader
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module Remove
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
include SalebotUploader::Uploader::Callbacks
|
7
|
+
|
8
|
+
##
|
9
|
+
# Removes the file and reset it
|
10
|
+
#
|
11
|
+
def remove!
|
12
|
+
with_callbacks(:remove) do
|
13
|
+
@file.delete if @file
|
14
|
+
@file = nil
|
15
|
+
@cache_id = nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end # Remove
|
20
|
+
end # Uploader
|
21
|
+
end # SalebotUploader
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'active_support/core_ext/hash'
|
3
|
+
|
4
|
+
module SalebotUploader
|
5
|
+
module Uploader
|
6
|
+
module Serialization
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
def serializable_hash(options = nil)
|
10
|
+
{'url' => url}.merge Hash[versions.map { |name, version| [name.to_s, { 'url' => version.url }] }]
|
11
|
+
end
|
12
|
+
|
13
|
+
def as_json(options=nil)
|
14
|
+
serializable_hash
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_json(options=nil)
|
18
|
+
JSON.generate(as_json)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_xml(options={})
|
22
|
+
merged_options = options.merge(:root => mounted_as || 'uploader', :type => 'uploader')
|
23
|
+
serializable_hash.to_xml(merged_options)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module Store
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
include SalebotUploader::Uploader::Callbacks
|
7
|
+
include SalebotUploader::Uploader::Configuration
|
8
|
+
include SalebotUploader::Uploader::Cache
|
9
|
+
|
10
|
+
included do
|
11
|
+
prepend Module.new {
|
12
|
+
def initialize(*)
|
13
|
+
super
|
14
|
+
@file, @filename, @cache_id, @identifier, @deduplication_index = nil
|
15
|
+
end
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Override this in your Uploader to change the filename.
|
21
|
+
#
|
22
|
+
# Be careful using record ids as filenames. If the filename is stored in the database
|
23
|
+
# the record id will be nil when the filename is set. Don't use record ids unless you
|
24
|
+
# understand this limitation.
|
25
|
+
#
|
26
|
+
# Do not use the version_name in the filename, as it will prevent versions from being
|
27
|
+
# loaded correctly.
|
28
|
+
#
|
29
|
+
# === Returns
|
30
|
+
#
|
31
|
+
# [String] a filename
|
32
|
+
#
|
33
|
+
def filename
|
34
|
+
@filename
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Returns a filename which doesn't conflict with already-stored files.
|
39
|
+
#
|
40
|
+
# === Returns
|
41
|
+
#
|
42
|
+
# [String] the filename with suffix added for deduplication
|
43
|
+
#
|
44
|
+
def deduplicated_filename
|
45
|
+
return unless filename
|
46
|
+
return filename unless @deduplication_index
|
47
|
+
|
48
|
+
parts = filename.split('.')
|
49
|
+
basename = parts.shift
|
50
|
+
basename.sub!(/ ?\(\d+\)\z/, '')
|
51
|
+
([basename.to_s + (@deduplication_index > 1 ? "(#{@deduplication_index})" : '')] + parts).join('.')
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Calculates the path where the file should be stored. If +for_file+ is given, it will be
|
56
|
+
# used as the identifier, otherwise +SalebotUploader::Uploader#identifier+ is assumed.
|
57
|
+
#
|
58
|
+
# === Parameters
|
59
|
+
#
|
60
|
+
# [for_file (String)] name of the file <optional>
|
61
|
+
#
|
62
|
+
# === Returns
|
63
|
+
#
|
64
|
+
# [String] the store path
|
65
|
+
#
|
66
|
+
def store_path(for_file=identifier)
|
67
|
+
File.join([store_dir, full_filename(for_file)].compact)
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Stores the file by passing it to this Uploader's storage engine.
|
72
|
+
#
|
73
|
+
# If new_file is omitted, a previously cached file will be stored.
|
74
|
+
#
|
75
|
+
# === Parameters
|
76
|
+
#
|
77
|
+
# [new_file (File, IOString, Tempfile)] any kind of file object
|
78
|
+
#
|
79
|
+
def store!(new_file=nil)
|
80
|
+
cache!(new_file) if new_file && !cached?
|
81
|
+
if !cache_only && @file && @cache_id
|
82
|
+
with_callbacks(:store, new_file) do
|
83
|
+
new_file = storage.store!(@file)
|
84
|
+
if delete_tmp_file_after_storage
|
85
|
+
@file.delete unless move_to_store
|
86
|
+
cache_storage.delete_dir!(cache_path(nil))
|
87
|
+
end
|
88
|
+
@file = new_file
|
89
|
+
@identifier = storage.identifier
|
90
|
+
@original_filename = @cache_id = @deduplication_index = nil
|
91
|
+
@staged = false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Retrieves the file from the storage.
|
98
|
+
#
|
99
|
+
# === Parameters
|
100
|
+
#
|
101
|
+
# [identifier (String)] uniquely identifies the file to retrieve
|
102
|
+
#
|
103
|
+
def retrieve_from_store!(identifier)
|
104
|
+
with_callbacks(:retrieve_from_store, identifier) do
|
105
|
+
@file = storage.retrieve!(identifier)
|
106
|
+
@identifier = identifier
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Look for an identifier which doesn't collide with the given already-stored identifiers.
|
112
|
+
# It is done by adding a index number as the suffix.
|
113
|
+
# For example, if there's 'image.jpg' and the @deduplication_index is set to 2,
|
114
|
+
# The stored file will be named as 'image(2).jpg'.
|
115
|
+
#
|
116
|
+
# === Parameters
|
117
|
+
#
|
118
|
+
# [current_identifiers (Array[String])] List of identifiers for already-stored files
|
119
|
+
#
|
120
|
+
def deduplicate(current_identifiers)
|
121
|
+
@deduplication_index = nil
|
122
|
+
return unless current_identifiers.include?(identifier)
|
123
|
+
|
124
|
+
(1..current_identifiers.size + 1).each do |i|
|
125
|
+
@deduplication_index = i
|
126
|
+
break unless current_identifiers.include?(identifier)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def full_filename(for_file)
|
133
|
+
forcing_extension(for_file)
|
134
|
+
end
|
135
|
+
|
136
|
+
def storage
|
137
|
+
@storage ||= self.class.storage.new(self)
|
138
|
+
end
|
139
|
+
|
140
|
+
end # Store
|
141
|
+
end # Uploader
|
142
|
+
end # SalebotUploader
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module SalebotUploader
|
2
|
+
module Uploader
|
3
|
+
module Url
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include SalebotUploader::Uploader::Configuration
|
6
|
+
include SalebotUploader::Utilities::Uri
|
7
|
+
|
8
|
+
##
|
9
|
+
# === Parameters
|
10
|
+
#
|
11
|
+
# [Hash] optional, the query params (only AWS)
|
12
|
+
#
|
13
|
+
# === Returns
|
14
|
+
#
|
15
|
+
# [String] the location where this file is accessible via a url
|
16
|
+
#
|
17
|
+
def url(options = {})
|
18
|
+
if file.respond_to?(:url)
|
19
|
+
tmp_url = file.method(:url).arity.zero? ? file.url : file.url(options)
|
20
|
+
return tmp_url if tmp_url.present?
|
21
|
+
end
|
22
|
+
|
23
|
+
if file.respond_to?(:path)
|
24
|
+
path = encode_path(file.path.sub(File.expand_path(root), ''))
|
25
|
+
|
26
|
+
if (host = asset_host)
|
27
|
+
if host.respond_to? :call
|
28
|
+
"#{host.call(file)}#{path}"
|
29
|
+
else
|
30
|
+
"#{host}#{path}"
|
31
|
+
end
|
32
|
+
else
|
33
|
+
(base_path || "") + path
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
url || ''
|
40
|
+
end
|
41
|
+
|
42
|
+
end # Url
|
43
|
+
end # Uploader
|
44
|
+
end # SalebotUploader
|