shrine 2.13.0 → 2.14.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of shrine might be problematic. Click here for more details.

Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -0
  3. data/README.md +20 -16
  4. data/doc/creating_storages.md +0 -21
  5. data/doc/design.md +1 -0
  6. data/doc/direct_s3.md +26 -15
  7. data/doc/metadata.md +67 -22
  8. data/doc/multiple_files.md +3 -3
  9. data/doc/processing.md +1 -1
  10. data/doc/retrieving_uploads.md +184 -0
  11. data/lib/shrine.rb +268 -900
  12. data/lib/shrine/attacher.rb +271 -0
  13. data/lib/shrine/attachment.rb +97 -0
  14. data/lib/shrine/plugins.rb +29 -0
  15. data/lib/shrine/plugins/_urlsafe_serialization.rb +182 -0
  16. data/lib/shrine/plugins/activerecord.rb +16 -14
  17. data/lib/shrine/plugins/add_metadata.rb +58 -24
  18. data/lib/shrine/plugins/backgrounding.rb +6 -1
  19. data/lib/shrine/plugins/cached_attachment_data.rb +9 -9
  20. data/lib/shrine/plugins/copy.rb +12 -8
  21. data/lib/shrine/plugins/data_uri.rb +23 -20
  22. data/lib/shrine/plugins/default_url_options.rb +5 -4
  23. data/lib/shrine/plugins/determine_mime_type.rb +24 -23
  24. data/lib/shrine/plugins/download_endpoint.rb +61 -73
  25. data/lib/shrine/plugins/migration_helpers.rb +17 -17
  26. data/lib/shrine/plugins/module_include.rb +9 -8
  27. data/lib/shrine/plugins/presign_endpoint.rb +13 -7
  28. data/lib/shrine/plugins/processing.rb +1 -1
  29. data/lib/shrine/plugins/rack_response.rb +128 -36
  30. data/lib/shrine/plugins/refresh_metadata.rb +20 -5
  31. data/lib/shrine/plugins/remote_url.rb +8 -8
  32. data/lib/shrine/plugins/remove_attachment.rb +9 -9
  33. data/lib/shrine/plugins/sequel.rb +21 -18
  34. data/lib/shrine/plugins/tempfile.rb +68 -0
  35. data/lib/shrine/plugins/upload_endpoint.rb +3 -2
  36. data/lib/shrine/plugins/upload_options.rb +7 -6
  37. data/lib/shrine/plugins/validation_helpers.rb +2 -1
  38. data/lib/shrine/storage/file_system.rb +20 -17
  39. data/lib/shrine/storage/linter.rb +0 -7
  40. data/lib/shrine/storage/s3.rb +159 -50
  41. data/lib/shrine/uploaded_file.rb +258 -0
  42. data/lib/shrine/version.rb +1 -1
  43. data/shrine.gemspec +7 -19
  44. metadata +41 -21
@@ -94,29 +94,31 @@ class Shrine
94
94
 
95
95
  return unless model < ::ActiveRecord::Base
96
96
 
97
- opts = shrine_class.opts
97
+ name = attachment_name
98
98
 
99
- model.class_eval <<-RUBY, __FILE__, __LINE__ + 1 if opts[:activerecord_validations]
100
- validate do
101
- #{@name}_attacher.errors.each do |message|
102
- errors.add(:#{@name}, *message)
99
+ if shrine_class.opts[:activerecord_validations]
100
+ model.validate do
101
+ send("#{name}_attacher").errors.each do |message|
102
+ errors.add(name, *message)
103
103
  end
104
104
  end
105
- RUBY
105
+ end
106
106
 
107
- model.class_eval <<-RUBY, __FILE__, __LINE__ + 1 if opts[:activerecord_callbacks]
108
- before_save do
109
- #{@name}_attacher.save if #{@name}_attacher.changed?
107
+ if shrine_class.opts[:activerecord_callbacks]
108
+ model.before_save do
109
+ attacher = send("#{name}_attacher")
110
+ attacher.save if attacher.changed?
110
111
  end
111
112
 
112
- after_commit on: [:create, :update] do
113
- #{@name}_attacher.finalize if #{@name}_attacher.changed?
113
+ model.after_commit on: [:create, :update] do
114
+ attacher = send("#{name}_attacher")
115
+ attacher.finalize if attacher.changed?
114
116
  end
115
117
 
116
- after_commit on: [:destroy] do
117
- #{@name}_attacher.destroy
118
+ model.after_commit on: [:destroy] do
119
+ send("#{name}_attacher").destroy
118
120
  end
119
- RUBY
121
+ end
120
122
  end
121
123
  end
122
124
 
@@ -56,19 +56,42 @@ class Shrine
56
56
  # "resolution" => movie.resolution,
57
57
  # "frame_rate" => movie.frame_rate }
58
58
  # end
59
+ #
60
+ # Any previously extracted metadata can be accessed via
61
+ # `context[:metadata]`:
62
+ #
63
+ # add_metadata :foo do |io, context|
64
+ # context[:metadata] #=>
65
+ # # {
66
+ # # "size" => 239823,
67
+ # # "filename" => "nature.jpg",
68
+ # # "mime_type" => "image/jpeg"
69
+ # # }
70
+ #
71
+ # "foo"
72
+ # end
73
+ #
74
+ # add_metadata :bar do |io, context|
75
+ # context[:metadata] #=>
76
+ # # {
77
+ # # "size" => 239823,
78
+ # # "filename" => "nature.jpg",
79
+ # # "mime_type" => "image/jpeg",
80
+ # # "foo" => "foo"
81
+ # # }
82
+ #
83
+ # "bar"
84
+ # end
59
85
  module AddMetadata
60
86
  def self.configure(uploader)
61
- uploader.opts[:metadata] = []
87
+ uploader.opts[:metadata] ||= []
62
88
  end
63
89
 
64
90
  module ClassMethods
65
- def add_metadata(name = nil, &block)
66
- if name
67
- opts[:metadata] << _metadata_proc(name, &block)
68
- metadata_method(name)
69
- else
70
- opts[:metadata] << block
71
- end
91
+ def add_metadata(name = nil, **options, &block)
92
+ opts[:metadata] << [name, options, block]
93
+
94
+ metadata_method(name) if name
72
95
  end
73
96
 
74
97
  def metadata_method(*names)
@@ -82,30 +105,41 @@ class Shrine
82
105
  metadata[name.to_s]
83
106
  end
84
107
  end
85
-
86
- def _metadata_proc(name, &block)
87
- proc do |io, context|
88
- value = instance_exec(io, context, &block)
89
- {name.to_s => value} unless value.nil?
90
- end
91
- end
92
108
  end
93
109
 
94
110
  module InstanceMethods
95
- def extract_metadata(io, context)
111
+ def extract_metadata(io, context = {})
96
112
  metadata = super
113
+ context = context.merge(metadata: metadata)
114
+
115
+ extract_custom_metadata(io, context)
116
+
117
+ metadata
118
+ end
119
+
120
+ private
121
+
122
+ def extract_custom_metadata(io, context)
123
+ opts[:metadata].each do |name, options, block|
124
+ result = instance_exec(io, context, &block)
125
+ metadata = {}
126
+
127
+ if name
128
+ metadata[name.to_s] = result
129
+ else
130
+ metadata.merge!(result) if result
131
+ end
97
132
 
98
- opts[:metadata].each do |metadata_block|
99
- custom_metadata = instance_exec(io, context, &metadata_block) || {}
100
- io.rewind
101
133
  # convert symbol keys to strings
102
- custom_metadata.keys.each do |key|
103
- custom_metadata[key.to_s] = custom_metadata.delete(key) if key.is_a?(Symbol)
134
+ metadata.keys.each do |key|
135
+ metadata[key.to_s] = metadata.delete(key) if key.is_a?(Symbol)
104
136
  end
105
- metadata.merge!(custom_metadata)
106
- end
107
137
 
108
- metadata
138
+ context[:metadata].merge!(metadata)
139
+
140
+ # rewind between metadata blocks
141
+ io.rewind
142
+ end
109
143
  end
110
144
  end
111
145
  end
@@ -265,7 +265,12 @@ class Shrine
265
265
  def swap(new_file)
266
266
  if self.class.respond_to?(:find_record)
267
267
  reloaded = self.class.find_record(record.class, record.id)
268
- return if reloaded.nil? || self.class.new(reloaded, name).read != read
268
+ return if reloaded.nil?
269
+
270
+ attacher = reloaded.send(:"#{name}_attacher") if reloaded.respond_to?(:"#{name}_attacher")
271
+ attacher ||= self.class.new(reloaded, name) # Shrine::Attachment is not used
272
+
273
+ return if attacher.get != self.get
269
274
  end
270
275
  super
271
276
  end
@@ -22,16 +22,16 @@ class Shrine
22
22
  def initialize(*)
23
23
  super
24
24
 
25
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
26
- def cached_#{@name}_data
27
- #{@name}_attacher.read_cached
28
- end
25
+ name = attachment_name
29
26
 
30
- def cached_#{@name}_data=(value)
31
- Shrine.deprecation("Calling #cached_#{@name}_data= is deprecated and will be removed in Shrine 3. You should use the original field name: `f.hidden_field :#{@name}, value: record.cached_#{@name}_data`.")
32
- #{@name}_attacher.assign(value)
33
- end
34
- RUBY
27
+ define_method :"cached_#{name}_data" do
28
+ send(:"#{name}_attacher").read_cached
29
+ end
30
+
31
+ define_method :"cached_#{name}_data=" do |value|
32
+ Shrine.deprecation("Calling #cached_#{name}_data= is deprecated and will be removed in Shrine 3. You should use the original field name: `f.hidden_field :#{name}, value: record.cached_#{name}_data`.")
33
+ send(:"#{name}_attacher").assign(value)
34
+ end
35
35
  end
36
36
  end
37
37
 
@@ -21,14 +21,18 @@ class Shrine
21
21
  def initialize(*)
22
22
  super
23
23
 
24
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
25
- def initialize_copy(record)
26
- super
27
- @#{@name}_attacher = nil # reload the attacher
28
- #{@name}_attacher.send(:write, nil) # remove original attachment
29
- #{@name}_attacher.copy(record.#{@name}_attacher)
30
- end
31
- RUBY
24
+ name = attachment_name
25
+
26
+ define_method :initialize_copy do |record|
27
+ super(record)
28
+ instance_variable_set(:"@#{name}_attacher", nil) # reload the attacher
29
+ attacher = send(:"#{name}_attacher")
30
+ attacher.send(:write, nil) # remove original attachment
31
+ attacher.copy(record.public_send(:"#{name}_attacher"))
32
+ end
33
+
34
+ # Fix for JRuby
35
+ private :initialize_copy
32
36
  end
33
37
  end
34
38
 
@@ -54,6 +54,7 @@ class Shrine
54
54
  # io = Shrine.data_uri("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA")
55
55
  # io.content_type #=> "image/png"
56
56
  # io.size #=> 21
57
+ # io.read # decoded content
57
58
  #
58
59
  # When the content type is ommited, `text/plain` is assumed. The parser
59
60
  # also supports raw data URIs which aren't base64-encoded.
@@ -64,6 +65,11 @@ class Shrine
64
65
  # io.size #=> 11
65
66
  # io.read #=> "raw content"
66
67
  #
68
+ # You can also assign a filename:
69
+ #
70
+ # io = Shrine.data_uri("data:,content", filename: "foo.txt")
71
+ # io.original_filename #=> "foo.txt"
72
+ #
67
73
  # ## `UploadedFile#data_uri` and `UploadedFile#base64`
68
74
  #
69
75
  # This plugin also adds UploadedFile#data_uri method, which returns a
@@ -94,15 +100,17 @@ class Shrine
94
100
  module ClassMethods
95
101
  # Parses the given data URI and creates an IO object from it.
96
102
  #
97
- # Shrine.data_uri("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA")
98
- # #=> #<Shrine::Plugins::DataUri::DataFile>
99
- def data_uri(uri)
103
+ # io = Shrine.data_uri("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA")
104
+ # io #=> #<Shrine::Plugins::DataUri::DataFile>
105
+ # io.content_type #=> "image/png"
106
+ # io.size #=> 21
107
+ # io.read # decoded content
108
+ def data_uri(uri, filename: nil)
100
109
  info = parse_data_uri(uri)
101
110
 
102
111
  content_type = info[:content_type] || DEFAULT_CONTENT_TYPE
103
112
  content = info[:base64] ? Base64.decode64(info[:data]) : CGI.unescape(info[:data])
104
- filename = opts[:data_uri_filename]
105
- filename = filename.call(content_type) if filename
113
+ filename = opts[:data_uri_filename].call(content_type) if opts[:data_uri_filename]
106
114
 
107
115
  data_file = DataFile.new(content, content_type: content_type, filename: filename)
108
116
  info[:data].clear
@@ -118,14 +126,9 @@ class Shrine
118
126
  media_type = scanner.scan(MEDIA_TYPE_REGEXP)
119
127
  base64 = scanner.scan(BASE64_REGEXP)
120
128
  scanner.scan(CONTENT_SEPARATOR) or raise ParseError, "data URI has invalid format"
129
+ content = scanner.post_match
121
130
 
122
- content_type = media_type[/^[^;]+/] if media_type
123
-
124
- {
125
- content_type: content_type,
126
- base64: !!base64,
127
- data: scanner.post_match,
128
- }
131
+ { content_type: media_type, base64: !!base64, data: content }
129
132
  end
130
133
  end
131
134
 
@@ -133,15 +136,15 @@ class Shrine
133
136
  def initialize(*)
134
137
  super
135
138
 
136
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
137
- def #{@name}_data_uri=(uri)
138
- #{@name}_attacher.data_uri = uri
139
- end
139
+ name = attachment_name
140
+
141
+ define_method :"#{name}_data_uri=" do |uri|
142
+ send(:"#{name}_attacher").data_uri = uri
143
+ end
140
144
 
141
- def #{@name}_data_uri
142
- #{@name}_attacher.data_uri
143
- end
144
- RUBY
145
+ define_method :"#{name}_data_uri" do
146
+ send(:"#{name}_attacher").data_uri
147
+ end
145
148
  end
146
149
  end
147
150
 
@@ -5,14 +5,14 @@ class Shrine
5
5
  # The `default_url_options` plugin allows you to specify URL options that
6
6
  # will be applied by default for uploaded files of specified storages.
7
7
  #
8
- # plugin :default_url_options, store: {download: true}
8
+ # plugin :default_url_options, store: { download: true }
9
9
  #
10
10
  # You can also generate the default URL options dynamically by using a
11
11
  # block, which will receive the UploadedFile object along with any options
12
12
  # that were passed to `UploadedFile#url`.
13
13
  #
14
- # plugin :default_url_options, store: ->(io, **options) do
15
- # {response_content_disposition: "attachment; filename=\"#{io.original_filename}\""}
14
+ # plugin :default_url_options, store: -> (io, **options) do
15
+ # { response_content_disposition: ContentDisposition.attachment(io.original_filename) }
16
16
  # end
17
17
  #
18
18
  # In both cases the default options are merged with options passed to
@@ -20,7 +20,8 @@ class Shrine
20
20
  # default options.
21
21
  module DefaultUrlOptions
22
22
  def self.configure(uploader, options = {})
23
- uploader.opts[:default_url_options] = (uploader.opts[:default_url_options] || {}).merge(options)
23
+ uploader.opts[:default_url_options] ||= {}
24
+ uploader.opts[:default_url_options].merge!(options)
24
25
  end
25
26
 
26
27
  module FileMethods
@@ -51,10 +51,10 @@ class Shrine
51
51
  # extension. Note that unlike other solutions, this analyzer is not
52
52
  # guaranteed to return the actual MIME type of the file.
53
53
  #
54
- # :default
55
- # : Uses the default way of extracting the MIME type, and that is reading
56
- # the `#content_type` attribute of the IO object, which might not hold
57
- # the actual MIME type of the file.
54
+ # :content_type
55
+ # : Retrieves the value of the `#content_type` attribute of the IO object.
56
+ # Note that this value normally comes from the "Content-Type" request
57
+ # header, so it's not guaranteed to hold the actual MIME type of the file.
58
58
  #
59
59
  # A single analyzer is not going to properly recognize all types of files,
60
60
  # so you can build your own custom analyzer for your requirements, where
@@ -88,6 +88,11 @@ class Shrine
88
88
  # [fastimage]: https://github.com/sdsykes/fastimage
89
89
  module DetermineMimeType
90
90
  def self.configure(uploader, opts = {})
91
+ if opts[:analyzer] == :default
92
+ Shrine.deprecation("The :default analyzer of the determine_mime_type plugin has been renamed to :content_type. The :default alias will not be supported in Shrine 3.")
93
+ opts = opts.merge(analyzer: :content_type)
94
+ end
95
+
91
96
  uploader.opts[:mime_type_analyzer] = opts.fetch(:analyzer, uploader.opts.fetch(:mime_type_analyzer, :file))
92
97
  end
93
98
 
@@ -95,16 +100,12 @@ class Shrine
95
100
  # Determines the MIME type of the IO object by calling the specified
96
101
  # analyzer.
97
102
  def determine_mime_type(io)
98
- if opts[:mime_type_analyzer] == :default
99
- mime_type = io.content_type if io.respond_to?(:content_type)
100
- else
101
- analyzer = opts[:mime_type_analyzer]
102
- analyzer = mime_type_analyzer(analyzer) if analyzer.is_a?(Symbol)
103
- args = [io, mime_type_analyzers].take(analyzer.arity.abs)
104
-
105
- mime_type = analyzer.call(*args)
106
- io.rewind
107
- end
103
+ analyzer = opts[:mime_type_analyzer]
104
+ analyzer = mime_type_analyzer(analyzer) if analyzer.is_a?(Symbol)
105
+ args = [io, mime_type_analyzers].take(analyzer.arity.abs)
106
+
107
+ mime_type = analyzer.call(*args)
108
+ io.rewind
108
109
 
109
110
  mime_type
110
111
  end
@@ -127,15 +128,9 @@ class Shrine
127
128
  module InstanceMethods
128
129
  private
129
130
 
130
- # Calls default behaviour when :default analyzer was specified, which
131
- # just reads the `#content_type` attribute, otherwise uses the specified
132
- # MIME type analyzer.
131
+ # Calls the configured MIME type analyzer.
133
132
  def extract_mime_type(io)
134
- if opts[:mime_type_analyzer] == :default
135
- super
136
- else
137
- self.class.determine_mime_type(io)
138
- end
133
+ self.class.determine_mime_type(io)
139
134
  end
140
135
 
141
136
  # Returns a hash of built-in MIME type analyzers.
@@ -145,7 +140,7 @@ class Shrine
145
140
  end
146
141
 
147
142
  class MimeTypeAnalyzer
148
- SUPPORTED_TOOLS = [:fastimage, :file, :filemagic, :mimemagic, :marcel, :mime_types, :mini_mime]
143
+ SUPPORTED_TOOLS = [:fastimage, :file, :filemagic, :mimemagic, :marcel, :mime_types, :mini_mime, :content_type]
149
144
  MAGIC_NUMBER = 256 * 1024
150
145
 
151
146
  def initialize(tool)
@@ -242,6 +237,12 @@ class Shrine
242
237
  end
243
238
  end
244
239
 
240
+ def extract_with_content_type(io)
241
+ if io.respond_to?(:content_type) && io.content_type
242
+ io.content_type.split(";").first
243
+ end
244
+ end
245
+
245
246
  def extract_filename(io)
246
247
  if io.respond_to?(:original_filename)
247
248
  io.original_filename
@@ -2,9 +2,6 @@
2
2
 
3
3
  require "roda"
4
4
 
5
- require "base64"
6
- require "json"
7
-
8
5
  class Shrine
9
6
  module Plugins
10
7
  # The `download_endpoint` plugin provides a Rack endpoint for downloading
@@ -41,15 +38,26 @@ class Shrine
41
38
  # Links to the download endpoint are generated by calling
42
39
  # `UploadedFile#download_url` instead of the usual `UploadedFile#url`.
43
40
  #
44
- # uploaded_file.download_url #=> "/attachments/eyJpZCI6ImFkdzlyeTM5ODJpandoYWla"
41
+ # uploaded_file.download_url #=> "/attachments/eyJpZCI6ImFkdzlyeTM..."
42
+ #
43
+ # ## Host
44
+ #
45
+ # You can specify download URL host via the `:host` plugin option:
46
+ #
47
+ # plugin :download_endpoint, host: "http://example.com"
48
+ #
49
+ # or by passing `:host` to `UploadedFile#download_url`:
45
50
  #
46
- # Note that streaming the file through your app might impact the request
47
- # throughput of your app, depending on which web server is used. It's
48
- # recommended to either configure a CDN to serve these files:
51
+ # uploaded_file.download_url(host: "http://example.com")
52
+ # #=> "http//example.com/attachments/eyJpZCI6ImFkdzlyeTM..."
49
53
  #
50
- # plugin :download_endpoint, host: "http://abc123.cloudfront.net"
54
+ # ## Performance considerations
51
55
  #
52
- # or configure the endpoint to redirect to the direct file URL:
56
+ # Streaming files through the app might impact the request throughput,
57
+ # depending on the web server you're using. So it's recommended to use a
58
+ # CDN, which can be set via the `:host` option.
59
+ #
60
+ # Alternatively, you can have the endpoint redirect to the direct file URL:
53
61
  #
54
62
  # plugin :download_endpoint, redirect: true
55
63
  # # or
@@ -57,13 +65,16 @@ class Shrine
57
65
  # # return URL which the request will redirect to
58
66
  # end
59
67
  #
60
- # Alternatively, you can stream files yourself from your controller using
61
- # the `rack_response` plugin, which this plugin uses internally.
68
+ # ## Custom endpoint
69
+ #
70
+ # If you want to have more control on download requests, you can use the
71
+ # `rack_response` plugin which this plugin uses internally.
62
72
  #
63
73
  # [Roda]: https://github.com/jeremyevans/roda
64
74
  module DownloadEndpoint
65
75
  def self.load_dependencies(uploader, opts = {})
66
76
  uploader.plugin :rack_response
77
+ uploader.plugin :_urlsafe_serialization
67
78
  end
68
79
 
69
80
  # Accepts the following options:
@@ -121,11 +132,7 @@ class Shrine
121
132
  @download_endpoint = endpoint_class
122
133
 
123
134
  const_set(:DownloadEndpoint, endpoint_class)
124
- deprecate_constant(:DownloadEndpoint) if RUBY_VERSION > "2.3"
125
- end
126
-
127
- def download_endpoint_serializer
128
- @download_endpoint_serializer ||= Serializer.new
135
+ deprecate_constant(:DownloadEndpoint)
129
136
  end
130
137
  end
131
138
 
@@ -142,34 +149,45 @@ class Shrine
142
149
  end
143
150
  end
144
151
 
145
- def download_url
146
- [download_host, *download_prefix, download_identifier].join("/")
152
+ # Returns file URL on the download endpoint.
153
+ def download_url(**options)
154
+ FileUrl.new(self).call(**options)
147
155
  end
148
156
 
149
157
  private
150
158
 
151
- # Generates URL-safe identifier from data, filtering only a subset of
152
- # metadata that the endpoint needs to prevent the URL from being too
153
- # long.
154
- def download_identifier
155
- semantical_metadata = metadata.select { |name, _| %w[filename size mime_type].include?(name) }
156
- download_serializer.dump(data.merge("metadata" => semantical_metadata.sort.to_h))
159
+ def download_storages
160
+ shrine_class.opts[:download_endpoint_storages]
161
+ end
162
+ end
163
+
164
+ class FileUrl
165
+ attr_reader :file
166
+
167
+ def initialize(file)
168
+ @file = file
157
169
  end
158
170
 
159
- def download_serializer
160
- shrine_class.download_endpoint_serializer
171
+ def call(host: self.host)
172
+ [host, *prefix, path].join("/")
161
173
  end
162
174
 
163
- def download_host
175
+ protected
176
+
177
+ def path
178
+ file.urlsafe_dump(metadata: %w[filename size mime_type])
179
+ end
180
+
181
+ def host
164
182
  shrine_class.opts[:download_endpoint_host]
165
183
  end
166
184
 
167
- def download_prefix
185
+ def prefix
168
186
  shrine_class.opts[:download_endpoint_prefix]
169
187
  end
170
188
 
171
- def download_storages
172
- shrine_class.opts[:download_endpoint_storages]
189
+ def shrine_class
190
+ file.shrine_class
173
191
  end
174
192
  end
175
193
 
@@ -181,23 +199,25 @@ class Shrine
181
199
  # handle legacy ":storage/:id" URLs
182
200
  r.on storage_names do |storage_name|
183
201
  r.get /(.*)/ do |id|
184
- data = { "id" => id, "storage" => storage_name, "metadata" => {} }
185
- serve_file(data)
202
+ uploaded_file = shrine_class::UploadedFile.new(
203
+ "id" => id,
204
+ "storage" => storage_name,
205
+ )
206
+
207
+ serve_file(uploaded_file)
186
208
  end
187
209
  end
188
210
 
189
- r.get /(.*)/ do |identifier|
190
- data = serializer.load(identifier)
191
- serve_file(data)
211
+ r.get /(.*)/ do |serialized|
212
+ uploaded_file = get_uploaded_file(serialized)
213
+ serve_file(uploaded_file)
192
214
  end
193
215
  end
194
216
 
195
217
  private
196
218
 
197
219
  # Streams or redirects to the uploaded file.
198
- def serve_file(data)
199
- uploaded_file = get_uploaded_file(data)
200
-
220
+ def serve_file(uploaded_file)
201
221
  if redirect
202
222
  redirect_to_file(uploaded_file)
203
223
  else
@@ -227,11 +247,11 @@ class Shrine
227
247
  end
228
248
 
229
249
  # Returns a Shrine::UploadedFile, or returns 404 if file doesn't exist.
230
- def get_uploaded_file(data)
231
- uploaded_file = shrine_class.uploaded_file(data)
250
+ def get_uploaded_file(serialized)
251
+ uploaded_file = shrine_class::UploadedFile.urlsafe_load(serialized)
232
252
  not_found! unless uploaded_file.exists?
233
253
  uploaded_file
234
- rescue Shrine::Error
254
+ rescue Shrine::Error # storage not found
235
255
  not_found!
236
256
  end
237
257
 
@@ -251,10 +271,6 @@ class Shrine
251
271
  shrine_class.storages.keys.map(&:to_s)
252
272
  end
253
273
 
254
- def serializer
255
- shrine_class.download_endpoint_serializer
256
- end
257
-
258
274
  def redirect
259
275
  opts[:redirect]
260
276
  end
@@ -267,34 +283,6 @@ class Shrine
267
283
  opts[:shrine_class]
268
284
  end
269
285
  end
270
-
271
- class Serializer
272
- def dump(data)
273
- base64_encode(json_encode(data))
274
- end
275
-
276
- def load(data)
277
- json_decode(base64_decode(data))
278
- end
279
-
280
- private
281
-
282
- def json_encode(data)
283
- JSON.generate(data)
284
- end
285
-
286
- def base64_encode(data)
287
- Base64.urlsafe_encode64(data)
288
- end
289
-
290
- def base64_decode(data)
291
- Base64.urlsafe_decode64(data)
292
- end
293
-
294
- def json_decode(data)
295
- JSON.parse(data)
296
- end
297
- end
298
286
  end
299
287
 
300
288
  register_plugin(:download_endpoint, DownloadEndpoint)