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