shrine 1.3.0 → 1.4.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.
- checksums.yaml +4 -4
- data/README.md +14 -10
- data/doc/carrierwave.md +2 -1
- data/doc/changing_location.md +15 -22
- data/doc/creating_storages.md +6 -2
- data/doc/direct_s3.md +19 -5
- data/doc/refile.md +1 -1
- data/lib/shrine/plugins/activerecord.rb +6 -12
- data/lib/shrine/plugins/backgrounding.rb +65 -31
- data/lib/shrine/plugins/backup.rb +28 -19
- data/lib/shrine/plugins/data_uri.rb +18 -8
- data/lib/shrine/plugins/delete_promoted.rb +21 -0
- data/lib/shrine/plugins/delete_raw.rb +38 -0
- data/lib/shrine/plugins/delete_uploaded.rb +3 -40
- data/lib/shrine/plugins/determine_mime_type.rb +13 -18
- data/lib/shrine/plugins/direct_upload.rb +133 -29
- data/lib/shrine/plugins/download_endpoint.rb +31 -12
- data/lib/shrine/plugins/hooks.rb +20 -46
- data/lib/shrine/plugins/keep_files.rb +7 -4
- data/lib/shrine/plugins/logging.rb +21 -15
- data/lib/shrine/plugins/migration_helpers.rb +37 -18
- data/lib/shrine/plugins/moving.rb +3 -2
- data/lib/shrine/plugins/parallelize.rb +36 -17
- data/lib/shrine/plugins/restore_cached.rb +3 -35
- data/lib/shrine/plugins/restore_cached_data.rb +34 -0
- data/lib/shrine/plugins/sequel.rb +5 -11
- data/lib/shrine/plugins/store_dimensions.rb +6 -4
- data/lib/shrine/plugins/validation_helpers.rb +4 -4
- data/lib/shrine/plugins/versions.rb +27 -16
- data/lib/shrine/storage/linter.rb +109 -58
- data/lib/shrine/storage/s3.rb +8 -7
- data/lib/shrine/version.rb +1 -1
- data/lib/shrine.rb +4 -4
- data/shrine.gemspec +1 -2
- metadata +10 -20
@@ -1,40 +1,3 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
# have been uploaded. This is especially useful when doing processing, to
|
5
|
-
# ensure that temporary files have been deleted after upload. One exception
|
6
|
-
# are `Shrine::UploadedFile` files, they won't get deleted for stability
|
7
|
-
# reasons.
|
8
|
-
#
|
9
|
-
# plugin :delete_uploaded
|
10
|
-
#
|
11
|
-
# By default this behaviour will be applied to all storages, but you can
|
12
|
-
# limit this only to specified storages:
|
13
|
-
#
|
14
|
-
# plugin :delete_uploaded, storages: [:store]
|
15
|
-
module DeleteUploaded
|
16
|
-
def self.configure(uploader, storages: :all)
|
17
|
-
uploader.opts[:delete_uploaded_storages] = storages
|
18
|
-
end
|
19
|
-
|
20
|
-
module InstanceMethods
|
21
|
-
private
|
22
|
-
|
23
|
-
# Deletes the uploaded file unless it's an UploadedFile.
|
24
|
-
def copy(io, context)
|
25
|
-
super
|
26
|
-
if io.respond_to?(:delete) && !io.is_a?(UploadedFile)
|
27
|
-
io.delete if delete_uploaded?(io)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def delete_uploaded?(io)
|
32
|
-
opts[:delete_uploaded_storages] == :all ||
|
33
|
-
opts[:delete_uploaded_storages].include?(storage_key)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
register_plugin(:delete_uploaded, DeleteUploaded)
|
39
|
-
end
|
40
|
-
end
|
1
|
+
warn "The delete_uploaded Shrine plugin has been renamed to \"delete_raw\". Loading the plugin through \"delete_uploaded\" will not work in Shrine 2."
|
2
|
+
require "shrine/plugins/delete_raw"
|
3
|
+
Shrine::Plugins.register_plugin(:delete_uploaded, Shrine::Plugins::DeleteRaw)
|
@@ -5,6 +5,11 @@ class Shrine
|
|
5
5
|
#
|
6
6
|
# plugin :determine_mime_type
|
7
7
|
#
|
8
|
+
# By default the UNIX [file] utility is used to determine the MIME type, but
|
9
|
+
# you can change it:
|
10
|
+
#
|
11
|
+
# plugin :determine_mime_type, analyzer: :filemagic
|
12
|
+
#
|
8
13
|
# The plugin accepts the following analyzers:
|
9
14
|
#
|
10
15
|
# :file
|
@@ -27,19 +32,10 @@ class Shrine
|
|
27
32
|
# *extension*. Note that unlike other solutions, this analyzer is not
|
28
33
|
# guaranteed to return the actual MIME type of the file.
|
29
34
|
#
|
30
|
-
# By default the UNIX [file] utility is used to detrmine the MIME type, but
|
31
|
-
# you can change it:
|
32
|
-
#
|
33
|
-
# plugin :determine_mime_type, analyzer: :filemagic
|
34
|
-
#
|
35
35
|
# If none of these quite suit your needs, you can use a custom analyzer:
|
36
36
|
#
|
37
37
|
# plugin :determine_mime_type, analyzer: ->(io) do
|
38
|
-
#
|
39
|
-
# "application/vnd.oasis.opendocument.text"
|
40
|
-
# else
|
41
|
-
# MimeMagic.by_magic(io).type
|
42
|
-
# end
|
38
|
+
# # returns the extracted MIME type
|
43
39
|
# end
|
44
40
|
#
|
45
41
|
# [file]: http://linux.die.net/man/1/file
|
@@ -77,13 +73,17 @@ class Shrine
|
|
77
73
|
def extract_mime_type(io)
|
78
74
|
analyzer = opts[:mime_type_analyzer]
|
79
75
|
|
80
|
-
if io.respond_to?(:mime_type)
|
76
|
+
mime_type = if io.respond_to?(:mime_type)
|
81
77
|
io.mime_type
|
82
78
|
elsif analyzer.is_a?(Symbol)
|
83
79
|
send(:"_extract_mime_type_with_#{analyzer}", io)
|
84
80
|
else
|
85
81
|
analyzer.call(io)
|
86
82
|
end
|
83
|
+
|
84
|
+
io.rewind
|
85
|
+
|
86
|
+
mime_type
|
87
87
|
end
|
88
88
|
|
89
89
|
private
|
@@ -98,7 +98,6 @@ class Shrine
|
|
98
98
|
mime_type, _ = Open3.capture2(*cmd, io.path)
|
99
99
|
else
|
100
100
|
mime_type, _ = Open3.capture2(*cmd, "-", stdin_data: io.read(MAGIC_NUMBER), binmode: true)
|
101
|
-
io.rewind
|
102
101
|
end
|
103
102
|
|
104
103
|
mime_type.strip unless mime_type.empty?
|
@@ -107,16 +106,12 @@ class Shrine
|
|
107
106
|
# Uses the ruby-filemagic gem to magically extract the MIME type.
|
108
107
|
def _extract_mime_type_with_filemagic(io)
|
109
108
|
filemagic = FileMagic.new(FileMagic::MAGIC_MIME_TYPE)
|
110
|
-
|
111
|
-
io.rewind
|
112
|
-
filemagic.buffer(data)
|
109
|
+
filemagic.buffer(io.read(MAGIC_NUMBER))
|
113
110
|
end
|
114
111
|
|
115
112
|
# Uses the mimemagic gem to extract the MIME type.
|
116
113
|
def _extract_mime_type_with_mimemagic(io)
|
117
|
-
|
118
|
-
io.rewind
|
119
|
-
result
|
114
|
+
MimeMagic.by_magic(io).type
|
120
115
|
end
|
121
116
|
|
122
117
|
# Uses the mime-types gem to determine MIME type from file extension.
|
@@ -18,10 +18,10 @@ class Shrine
|
|
18
18
|
#
|
19
19
|
# You should always mount a new endpoint for each uploader that you want to
|
20
20
|
# enable direct uploads for. This now gives your Ruby application a `POST
|
21
|
-
# /attachments/images/:storage
|
21
|
+
# /attachments/images/:storage/upload` route, which accepts a "file" query
|
22
22
|
# parameter, and returns the uploaded file in JSON format:
|
23
23
|
#
|
24
|
-
# # POST /attachments/images/cache/
|
24
|
+
# # POST /attachments/images/cache/upload (file upload)
|
25
25
|
# {
|
26
26
|
# "id": "43kewit94.jpg",
|
27
27
|
# "storage": "cache",
|
@@ -50,8 +50,9 @@ class Shrine
|
|
50
50
|
# ## Presigning
|
51
51
|
#
|
52
52
|
# An alternative to the direct endpoint is uploading directly to the
|
53
|
-
# underlying storage (S3). These uploads
|
54
|
-
# from the server, you can enable that
|
53
|
+
# underlying storage (currently only supported by Amazon S3). These uploads
|
54
|
+
# usually require extra information from the server, you can enable that
|
55
|
+
# route by passing `presign: true`:
|
55
56
|
#
|
56
57
|
# plugin :direct_upload, presign: true
|
57
58
|
#
|
@@ -78,24 +79,38 @@ class Shrine
|
|
78
79
|
#
|
79
80
|
# GET /cache/presign?extension=.png
|
80
81
|
#
|
82
|
+
# You can change how the key is generated with `:presign_location`:
|
83
|
+
#
|
84
|
+
# plugin :direct_upload, presign: true, presign_location: ->(request) { "${filename}" }
|
85
|
+
#
|
81
86
|
# If you want additional options to be passed to Storage::S3#presign, you
|
82
|
-
# can pass
|
87
|
+
# can pass `:presign_options` with a hash or a block (which gets yielded
|
88
|
+
# Roda's request object):
|
83
89
|
#
|
84
|
-
# plugin :direct_upload, presign:
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
90
|
+
# plugin :direct_upload, presign: true, presign_options: {acl: "public-read"}
|
91
|
+
#
|
92
|
+
# plugin :direct_upload, presign: true, presign_options: ->(request) do
|
93
|
+
# options = {}
|
94
|
+
# options[:content_length_range] = 0..(5*1024*1024) # limit the filesize to 5 MB
|
95
|
+
# options[:content_type] = request.params["content_type"] # use "content_type" query parameter
|
96
|
+
# options
|
89
97
|
# end
|
90
98
|
#
|
91
99
|
# See the [Direct Uploads to S3] guide for further instructions on how to
|
92
|
-
# hook
|
100
|
+
# hook the presigned uploads to a form.
|
101
|
+
#
|
102
|
+
# ### Testing presigns
|
103
|
+
#
|
104
|
+
# If you want to test presigned uploads, but don't want to pay the
|
105
|
+
# performance cost of using Amazon S3 storage in tests, you can simply swap
|
106
|
+
# out S3 with a storage like FileSystem. The presigns will still get
|
107
|
+
# generated, but will simply point to this endpoint's upload route.
|
93
108
|
#
|
94
109
|
# ## Allowed storages
|
95
110
|
#
|
96
111
|
# While Shrine only accepts cached attachments on form submits (for security
|
97
112
|
# reasons), you can use this endpoint to upload files to any storage, just
|
98
|
-
# add it
|
113
|
+
# add it to allowed storages:
|
99
114
|
#
|
100
115
|
# plugin :direct_upload, allowed_storages: [:cache, :store]
|
101
116
|
#
|
@@ -141,9 +156,17 @@ class Shrine
|
|
141
156
|
uploader.plugin :rack_file
|
142
157
|
end
|
143
158
|
|
144
|
-
def self.configure(uploader, allowed_storages: [:cache], presign: nil, max_size: nil)
|
159
|
+
def self.configure(uploader, allowed_storages: [:cache], presign: nil, presign_options: {}, presign_location: nil, max_size: nil)
|
160
|
+
if presign.respond_to?(:call)
|
161
|
+
warn "Passing a block to :presign in direct_upload plugin is deprecated and will be removed in Shrine 2. Use :presign_options instead."
|
162
|
+
presign_options = presign
|
163
|
+
presign = true
|
164
|
+
end
|
165
|
+
|
145
166
|
uploader.opts[:direct_upload_allowed_storages] = allowed_storages
|
146
167
|
uploader.opts[:direct_upload_presign] = presign
|
168
|
+
uploader.opts[:direct_upload_presign_options] = presign_options
|
169
|
+
uploader.opts[:direct_upload_presign_location] = presign_location
|
147
170
|
uploader.opts[:direct_upload_max_size] = max_size
|
148
171
|
|
149
172
|
uploader.assign_upload_endpoint(App) unless uploader.const_defined?(:UploadEndpoint)
|
@@ -165,7 +188,7 @@ class Shrine
|
|
165
188
|
|
166
189
|
# Returns the Roda direct upload endpoint.
|
167
190
|
def direct_endpoint
|
168
|
-
warn "
|
191
|
+
warn "Shrine.direct_endpoint is deprecated and will be removed in Shrine 2, you should use Shrine::UploadEndpoint instead."
|
169
192
|
self::UploadEndpoint
|
170
193
|
end
|
171
194
|
end
|
@@ -175,36 +198,109 @@ class Shrine
|
|
175
198
|
# with the file upload and returns the uploaded file as JSON.
|
176
199
|
class App < Roda
|
177
200
|
plugin :default_headers, "Content-Type"=>"application/json"
|
201
|
+
plugin :json_parser
|
178
202
|
|
179
203
|
route do |r|
|
180
204
|
r.on ":storage" do |storage_key|
|
181
|
-
|
182
|
-
@uploader = shrine_class.new(storage_key.to_sym)
|
205
|
+
@uploader = get_uploader(storage_key)
|
183
206
|
|
184
|
-
r.post ":name" do |name|
|
207
|
+
r.post ["upload", ":name"] do |name|
|
185
208
|
file = get_file
|
186
|
-
context =
|
209
|
+
context = get_context(name)
|
187
210
|
|
188
|
-
|
189
|
-
|
211
|
+
uploaded_file = upload(file, context)
|
212
|
+
|
213
|
+
json uploaded_file
|
214
|
+
end unless presign? && presign_storage?
|
190
215
|
|
191
216
|
r.get "presign" do
|
192
|
-
location =
|
193
|
-
options =
|
217
|
+
location = get_presign_location
|
218
|
+
options = get_presign_options
|
194
219
|
|
195
|
-
|
220
|
+
presign_data = generate_presign(location, options)
|
196
221
|
|
197
|
-
json
|
198
|
-
end if presign
|
222
|
+
json presign_data
|
223
|
+
end if presign?
|
199
224
|
end
|
200
225
|
end
|
201
226
|
|
202
227
|
private
|
203
228
|
|
229
|
+
attr_reader :uploader
|
230
|
+
|
231
|
+
# Instantiates the uploader, checking first if the storage is allowed.
|
232
|
+
def get_uploader(storage_key)
|
233
|
+
allow_storage!(storage_key)
|
234
|
+
shrine_class.new(storage_key.to_sym)
|
235
|
+
end
|
236
|
+
|
237
|
+
# Retrieves the context for the upload.
|
238
|
+
def get_context(name)
|
239
|
+
context = {phase: :cache}
|
240
|
+
|
241
|
+
if name != "upload"
|
242
|
+
warn "The \"POST /:storage/:name\" route of the direct_upload Shrine plugin is deprecated, and it will be removed in Shrine 3. Use \"POST /:storage/upload\" instead."
|
243
|
+
context[:name] = name
|
244
|
+
end
|
245
|
+
|
246
|
+
if presign? && !presign_storage?
|
247
|
+
context[:location] = request.params["key"]
|
248
|
+
end
|
249
|
+
|
250
|
+
context
|
251
|
+
end
|
252
|
+
|
253
|
+
# Uploads the file to the requested storage.
|
254
|
+
def upload(file, context)
|
255
|
+
uploader.upload(file, context)
|
256
|
+
end
|
257
|
+
|
258
|
+
# Generates a unique location, or calls `:presign_location`.
|
259
|
+
def get_presign_location
|
260
|
+
if presign_location
|
261
|
+
presign_location.call(request)
|
262
|
+
else
|
263
|
+
SecureRandom.hex(30) + request.params["extension"].to_s
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# Returns dynamic options for generating the presign.
|
268
|
+
def get_presign_options
|
269
|
+
options = presign_options
|
270
|
+
options = options.call(request) if options.respond_to?(:call)
|
271
|
+
options || {}
|
272
|
+
end
|
273
|
+
|
274
|
+
# Generates the presign hash for the request.
|
275
|
+
def generate_presign(location, options)
|
276
|
+
if presign_storage?
|
277
|
+
generate_real_presign(location, options)
|
278
|
+
else
|
279
|
+
generate_fake_presign(location, options)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
# Generates a presign by calling the storage.
|
284
|
+
def generate_real_presign(location, options)
|
285
|
+
signature = uploader.storage.presign(location, options)
|
286
|
+
{url: signature.url, fields: signature.fields}
|
287
|
+
end
|
288
|
+
|
289
|
+
# Generates a presign that points to the direct upload endpoint.
|
290
|
+
def generate_fake_presign(location, options)
|
291
|
+
url = request.url.sub(/presign$/, "upload")
|
292
|
+
{url: url, fields: {key: location}}
|
293
|
+
end
|
294
|
+
|
295
|
+
# Returns true if the storage supports presigns.
|
296
|
+
def presign_storage?
|
297
|
+
uploader.storage.respond_to?(:presign)
|
298
|
+
end
|
299
|
+
|
204
300
|
# Halts the request if storage is not allowed.
|
205
|
-
def allow_storage!(
|
206
|
-
if !allowed_storages.map(&:to_s).include?(
|
207
|
-
error! 403, "Storage #{
|
301
|
+
def allow_storage!(storage_key)
|
302
|
+
if !allowed_storages.map(&:to_s).include?(storage_key)
|
303
|
+
error! 403, "Storage #{storage_key.inspect} is not allowed."
|
208
304
|
end
|
209
305
|
end
|
210
306
|
|
@@ -253,10 +349,18 @@ class Shrine
|
|
253
349
|
shrine_class.opts[:direct_upload_allowed_storages]
|
254
350
|
end
|
255
351
|
|
256
|
-
def presign
|
352
|
+
def presign?
|
257
353
|
shrine_class.opts[:direct_upload_presign]
|
258
354
|
end
|
259
355
|
|
356
|
+
def presign_options
|
357
|
+
shrine_class.opts[:direct_upload_presign_options]
|
358
|
+
end
|
359
|
+
|
360
|
+
def presign_location
|
361
|
+
shrine_class.opts[:direct_upload_presign_location]
|
362
|
+
end
|
363
|
+
|
260
364
|
def max_size
|
261
365
|
shrine_class.opts[:direct_upload_max_size]
|
262
366
|
end
|
@@ -97,8 +97,7 @@ class Shrine
|
|
97
97
|
|
98
98
|
route do |r|
|
99
99
|
r.on ":storage" do |storage_key|
|
100
|
-
|
101
|
-
@storage = shrine_class.find_storage(storage_key)
|
100
|
+
@storage = get_storage(storage_key)
|
102
101
|
|
103
102
|
r.get /(.*)/ do |id|
|
104
103
|
filename = request.path.split("/").last
|
@@ -107,14 +106,13 @@ class Shrine
|
|
107
106
|
response["Content-Disposition"] = "#{disposition}; filename=#{filename.inspect}"
|
108
107
|
response["Content-Type"] = Rack::Mime.mime_type(extname)
|
109
108
|
|
109
|
+
chunks = get_stream(id)
|
110
|
+
_, content_length = chunks.peek
|
111
|
+
response['Content-Length'] = content_length.to_s if content_length
|
112
|
+
|
110
113
|
stream do |out|
|
111
|
-
|
112
|
-
|
113
|
-
else
|
114
|
-
io, buffer = @storage.open(id), ""
|
115
|
-
out << io.read(16384, buffer) until io.eof?
|
116
|
-
io.close
|
117
|
-
io.delete if io.class.name == "Tempfile"
|
114
|
+
chunks.each do |chunk|
|
115
|
+
out << chunk
|
118
116
|
end
|
119
117
|
end
|
120
118
|
end
|
@@ -123,10 +121,31 @@ class Shrine
|
|
123
121
|
|
124
122
|
private
|
125
123
|
|
124
|
+
attr_reader :storage
|
125
|
+
|
126
|
+
def get_storage(storage_key)
|
127
|
+
allow_storage!(storage_key)
|
128
|
+
shrine_class.find_storage(storage_key)
|
129
|
+
end
|
130
|
+
|
131
|
+
def get_stream(id)
|
132
|
+
if storage.respond_to?(:stream)
|
133
|
+
storage.enum_for(:stream, id)
|
134
|
+
else
|
135
|
+
Enumerator.new do |y|
|
136
|
+
io = storage.open(id)
|
137
|
+
buffer = ""
|
138
|
+
y.yield(io.read(16*1024, buffer), io.size) until io.eof?
|
139
|
+
io.close
|
140
|
+
io.delete if io.class.name == "Tempfile"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
126
145
|
# Halts the request if storage is not allowed.
|
127
|
-
def allow_storage!(
|
128
|
-
if !allowed_storages.map(&:to_s).include?(
|
129
|
-
error! 403, "Storage #{
|
146
|
+
def allow_storage!(storage_key)
|
147
|
+
if !allowed_storages.map(&:to_s).include?(storage_key)
|
148
|
+
error! 403, "Storage #{storage_key.inspect} is not allowed."
|
130
149
|
end
|
131
150
|
end
|
132
151
|
|
data/lib/shrine/plugins/hooks.rb
CHANGED
@@ -22,24 +22,21 @@ class Shrine
|
|
22
22
|
#
|
23
23
|
# Shrine calls hooks in the following order when uploading a file:
|
24
24
|
#
|
25
|
+
# * `before_upload`
|
25
26
|
# * `around_upload`
|
26
|
-
# * `
|
27
|
+
# * `before_process`
|
27
28
|
# * `around_process`
|
28
|
-
#
|
29
|
-
#
|
30
|
-
# * `after_process`
|
29
|
+
# * `after_process`
|
30
|
+
# * `before_store`
|
31
31
|
# * `around_store`
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# * `after_store`
|
35
|
-
# * `after_upload`
|
32
|
+
# * `after_store`
|
33
|
+
# * `after_upload`
|
36
34
|
#
|
37
35
|
# Shrine calls hooks in the following order when deleting a file:
|
38
36
|
#
|
37
|
+
# * `before_delete`
|
39
38
|
# * `around_delete`
|
40
|
-
#
|
41
|
-
# * DELETE
|
42
|
-
# * `after_delete`
|
39
|
+
# * `after_delete`
|
43
40
|
#
|
44
41
|
# By default every `around_*` hook returns the result of the corresponding
|
45
42
|
# operation:
|
@@ -51,39 +48,18 @@ class Shrine
|
|
51
48
|
# result # it's good to always return the result for consistent behaviour
|
52
49
|
# end
|
53
50
|
# end
|
54
|
-
#
|
55
|
-
# It may be useful to know that you can realize some form of communication
|
56
|
-
# between the hooks; whatever you save to the `context` hash will be
|
57
|
-
# forwarded further down:
|
58
|
-
#
|
59
|
-
# class ImageUploader < Shrine
|
60
|
-
# def before_process(io, context)
|
61
|
-
# context[:_foo] = "bar"
|
62
|
-
# super
|
63
|
-
# end
|
64
|
-
#
|
65
|
-
# def before_store(io, context)
|
66
|
-
# context[:_foo] #=> "bar"
|
67
|
-
# super
|
68
|
-
# end
|
69
|
-
# end
|
70
|
-
#
|
71
|
-
# In that case you should always somehow mark this key as private (for
|
72
|
-
# example with an underscore) so that it doesn't clash with any
|
73
|
-
# existing keys.
|
74
51
|
module Hooks
|
75
52
|
module InstanceMethods
|
76
53
|
def upload(io, context = {})
|
77
54
|
result = nil
|
55
|
+
before_upload(io, context)
|
78
56
|
around_upload(io, context) { result = super }
|
57
|
+
after_upload(io, context)
|
79
58
|
result
|
80
59
|
end
|
81
60
|
|
82
61
|
def around_upload(*args)
|
83
|
-
|
84
|
-
result = yield
|
85
|
-
after_upload(*args)
|
86
|
-
result
|
62
|
+
yield
|
87
63
|
end
|
88
64
|
|
89
65
|
def before_upload(*)
|
@@ -95,16 +71,15 @@ class Shrine
|
|
95
71
|
|
96
72
|
def processed(io, context)
|
97
73
|
result = nil
|
74
|
+
before_process(io, context)
|
98
75
|
around_process(io, context) { result = super }
|
76
|
+
after_process(io, context)
|
99
77
|
result
|
100
78
|
end
|
101
79
|
private :processed
|
102
80
|
|
103
81
|
def around_process(*args)
|
104
|
-
|
105
|
-
result = yield
|
106
|
-
after_process(*args)
|
107
|
-
result
|
82
|
+
yield
|
108
83
|
end
|
109
84
|
|
110
85
|
def before_process(*)
|
@@ -116,15 +91,14 @@ class Shrine
|
|
116
91
|
|
117
92
|
def store(io, context = {})
|
118
93
|
result = nil
|
94
|
+
before_store(io, context)
|
119
95
|
around_store(io, context) { result = super }
|
96
|
+
after_store(io, context)
|
120
97
|
result
|
121
98
|
end
|
122
99
|
|
123
100
|
def around_store(*args)
|
124
|
-
|
125
|
-
result = yield
|
126
|
-
after_store(*args)
|
127
|
-
result
|
101
|
+
yield
|
128
102
|
end
|
129
103
|
|
130
104
|
def before_store(*)
|
@@ -136,14 +110,14 @@ class Shrine
|
|
136
110
|
|
137
111
|
def delete(io, context = {})
|
138
112
|
result = nil
|
113
|
+
before_delete(io, context)
|
139
114
|
around_delete(io, context) { result = super }
|
115
|
+
after_delete(io, context)
|
140
116
|
result
|
141
117
|
end
|
142
118
|
|
143
119
|
def around_delete(*args)
|
144
|
-
|
145
|
-
result = yield
|
146
|
-
after_delete(*args)
|
120
|
+
yield
|
147
121
|
end
|
148
122
|
|
149
123
|
def before_delete(*)
|
@@ -26,10 +26,13 @@ class Shrine
|
|
26
26
|
uploader.opts[:keep_files] << :replaced if replaced
|
27
27
|
end
|
28
28
|
|
29
|
-
module
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
module AttacherMethods
|
30
|
+
def replace
|
31
|
+
super unless shrine_class.opts[:keep_files].include?(:replaced)
|
32
|
+
end
|
33
|
+
|
34
|
+
def destroy
|
35
|
+
super unless shrine_class.opts[:keep_files].include?(:destroyed)
|
33
36
|
end
|
34
37
|
end
|
35
38
|
end
|
@@ -12,8 +12,8 @@ class Shrine
|
|
12
12
|
# going on, or you simply want to have it logged for future debugging.
|
13
13
|
# By default the logging output looks something like this:
|
14
14
|
#
|
15
|
-
# 2015-10-09T20:06:06.676Z #25602:
|
16
|
-
# 2015-10-09T20:06:06.854Z #25602: PROCESS[
|
15
|
+
# 2015-10-09T20:06:06.676Z #25602: STORE[cache] ImageUploader[:avatar] User[29543] 1 file (0.1s)
|
16
|
+
# 2015-10-09T20:06:06.854Z #25602: PROCESS[store]: ImageUploader[:avatar] User[29543] 1-3 files (0.22s)
|
17
17
|
# 2015-10-09T20:06:07.133Z #25602: DELETE[destroyed]: ImageUploader[:avatar] User[29543] 3 files (0.07s)
|
18
18
|
#
|
19
19
|
# The plugin accepts the following options:
|
@@ -36,16 +36,20 @@ class Shrine
|
|
36
36
|
# grep. If this is important to you, you can switch to another format:
|
37
37
|
#
|
38
38
|
# plugin :logging, format: :json
|
39
|
-
# # {"action":"upload","phase":"
|
39
|
+
# # {"action":"upload","phase":"cache","uploader":"ImageUploader","attachment":"avatar",...}
|
40
40
|
#
|
41
41
|
# plugin :logging, format: :heroku
|
42
|
-
# # action=upload phase=
|
42
|
+
# # action=upload phase=cache uploader=ImageUploader attachment=avatar record_class=User ...
|
43
43
|
#
|
44
44
|
# Logging is by default disabled in tests, but you can enable it by setting
|
45
45
|
# `Shrine.logger.level = Logger::INFO`.
|
46
46
|
module Logging
|
47
|
+
def self.load_dependencies(uploader, *)
|
48
|
+
uploader.plugin :hooks
|
49
|
+
end
|
50
|
+
|
47
51
|
def self.configure(uploader, logger: nil, stream: $stdout, format: :human)
|
48
|
-
uploader.
|
52
|
+
uploader.opts[:logging_logger] = logger
|
49
53
|
uploader.opts[:logging_stream] = stream
|
50
54
|
uploader.opts[:logging_format] = format
|
51
55
|
end
|
@@ -57,7 +61,7 @@ class Shrine
|
|
57
61
|
|
58
62
|
# Initializes a new logger if it hasn't been initialized.
|
59
63
|
def logger
|
60
|
-
@logger ||= (
|
64
|
+
@logger ||= opts[:logging_logger] || (
|
61
65
|
logger = Logger.new(opts[:logging_stream])
|
62
66
|
logger.level = Logger::INFO
|
63
67
|
logger.level = Logger::WARN if ENV["RACK_ENV"] == "test"
|
@@ -78,22 +82,22 @@ class Shrine
|
|
78
82
|
end
|
79
83
|
|
80
84
|
module InstanceMethods
|
81
|
-
def
|
85
|
+
def around_process(io, context)
|
86
|
+
log("process", io, context) { super }
|
87
|
+
end
|
88
|
+
|
89
|
+
def around_store(io, context)
|
82
90
|
log("store", io, context) { super }
|
83
91
|
end
|
84
92
|
|
85
|
-
def
|
93
|
+
def around_delete(io, context)
|
86
94
|
log("delete", io, context) { super }
|
87
95
|
end
|
88
96
|
|
89
97
|
private
|
90
98
|
|
91
|
-
def processed(io, context = {})
|
92
|
-
log("process", io, context) { super }
|
93
|
-
end
|
94
|
-
|
95
99
|
# Collects the data and sends it for logging.
|
96
|
-
def log(action,
|
100
|
+
def log(action, input, context)
|
97
101
|
result, duration = benchmark { yield }
|
98
102
|
|
99
103
|
_log(
|
@@ -103,7 +107,7 @@ class Shrine
|
|
103
107
|
attachment: context[:name],
|
104
108
|
record_class: (context[:record].class if context[:record]),
|
105
109
|
record_id: (context[:record].id if context[:record].respond_to?(:id)),
|
106
|
-
files: count(
|
110
|
+
files: (action == "process" ? [count(input), count(result)] : count(result)),
|
107
111
|
duration: ("%.2f" % duration).to_f,
|
108
112
|
) unless result.nil?
|
109
113
|
|
@@ -124,16 +128,18 @@ class Shrine
|
|
124
128
|
components.last << "[:#{data[:attachment]}]" if data[:attachment]
|
125
129
|
components << "#{data[:record_class]}" if data[:record_class]
|
126
130
|
components.last << "[#{data[:record_id]}]" if data[:record_id]
|
127
|
-
components << (data[:files]
|
131
|
+
components << "#{Array(data[:files]).join("-")} #{"file#{"s" if Array(data[:files]).any?{|n| n > 1}}"}"
|
128
132
|
components << "(#{data[:duration]}s)"
|
129
133
|
components.join(" ")
|
130
134
|
end
|
131
135
|
|
132
136
|
def _log_message_json(data)
|
137
|
+
data[:files] = Array(data[:files]).join("-")
|
133
138
|
data.to_json
|
134
139
|
end
|
135
140
|
|
136
141
|
def _log_message_heroku(data)
|
142
|
+
data[:files] = Array(data[:files]).join("-")
|
137
143
|
data.map { |key, value| "#{key}=#{value}" }.join(" ")
|
138
144
|
end
|
139
145
|
|