kithe 2.0.0.pre.alpha2 → 2.0.0.pre.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/jobs/kithe/create_derivatives_job.rb +2 -2
- data/app/models/kithe/asset.rb +82 -154
- data/app/models/kithe/asset/derivative_creator.rb +32 -62
- data/app/models/kithe/asset/derivative_definition.rb +12 -13
- data/app/models/kithe/asset/set_shrine_uploader.rb +64 -0
- data/app/models/kithe/collection.rb +0 -6
- data/app/models/kithe/model.rb +0 -21
- data/app/models/kithe/work.rb +0 -5
- data/app/uploaders/kithe/asset_uploader.rb +15 -78
- data/lib/kithe/version.rb +4 -1
- data/lib/shrine/plugins/kithe_checksum_signatures.rb +41 -0
- data/lib/shrine/plugins/kithe_controllable_backgrounding.rb +53 -0
- data/lib/shrine/plugins/kithe_derivative_definitions.rb +101 -0
- data/lib/shrine/plugins/kithe_derivatives.rb +54 -0
- data/lib/shrine/plugins/kithe_determine_mime_type.rb +39 -0
- data/lib/shrine/plugins/kithe_persisted_derivatives.rb +161 -0
- data/lib/shrine/plugins/kithe_promotion_callbacks.rb +4 -0
- data/lib/shrine/plugins/kithe_promotion_directives.rb +33 -3
- data/lib/shrine/plugins/kithe_storage_location.rb +53 -4
- data/lib/tasks/kithe_tasks.rake +22 -15
- data/spec/dummy/log/development.log +867 -0
- data/spec/dummy/log/test.log +26005 -0
- data/spec/models/kithe/asset/asset_derivatives_spec.rb +137 -0
- data/spec/models/kithe/asset/asset_promotion_hooks_spec.rb +26 -5
- data/spec/models/kithe/asset/set_shrine_uploader_spec.rb +39 -0
- data/spec/models/kithe/asset_spec.rb +9 -59
- data/spec/models/kithe/model_spec.rb +0 -32
- data/spec/shrine/kithe_accept_remote_url_spec.rb +49 -0
- data/spec/shrine/kithe_checksum_signatures_spec.rb +63 -0
- data/spec/shrine/kithe_derivative_definitions_spec.rb +303 -0
- data/spec/shrine/kithe_persisted_derivatives_spec.rb +424 -0
- data/spec/shrine/kithe_storage_location_spec.rb +43 -15
- data/spec/spec_helper.rb +7 -6
- data/spec/test_support/images/3x3_pixel.jpg +0 -0
- data/spec/test_support/shrine_spec_support.rb +2 -1
- metadata +23 -23
- data/app/models/kithe/asset/derivative_updater.rb +0 -119
- data/app/models/kithe/derivative.rb +0 -15
- data/app/uploaders/kithe/derivative_uploader.rb +0 -48
- data/spec/models/kithe/asset/asset_create_derivatives_spec.rb +0 -320
- data/spec/models/kithe/derivative_spec.rb +0 -168
@@ -4,29 +4,57 @@ require 'shrine/plugins/kithe_storage_location'
|
|
4
4
|
describe Shrine::Plugins::KitheStorageLocation do
|
5
5
|
let(:uploader) { test_uploader { plugin :kithe_storage_location } }
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
describe "for main file" do
|
8
|
+
it "uploads with a record with id" do
|
9
|
+
uploaded_file = uploader.upload(fakeio, record: OpenStruct.new(id: "81060886-4f93-42e7-ace7-ab51399f4808"), name: :file)
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
expect(uploaded_file.id).to match %r{\Aasset/81060886-4f93-42e7-ace7-ab51399f4808/[0-9a-f]+}
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
it "has suffix with a record id and filename" do
|
15
|
+
uploaded_file = uploader.upload(fakeio(filename: "foo.jpg"), record: OpenStruct.new(id: "81060886-4f93-42e7-ace7-ab51399f4808"), name: :file)
|
15
16
|
|
16
|
-
|
17
|
-
|
17
|
+
expect(uploaded_file.id).to match %r{\Aasset/81060886-4f93-42e7-ace7-ab51399f4808/[0-9a-f]+\.jpg}
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
20
|
+
it "uploads with no record" do
|
21
|
+
uploaded_file = uploader.upload(fakeio, record: OpenStruct.new(id: "81060886-4f93-42e7-ace7-ab51399f4808"), name: :file)
|
21
22
|
|
22
|
-
|
23
|
-
|
23
|
+
expect(uploaded_file.id).to match %r{\Aasset/[0-9a-f]+}
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
26
|
+
it "uploads with record with no id" do
|
27
|
+
uploaded_file = uploader.upload(fakeio, record: OpenStruct.new(), name: :file)
|
27
28
|
|
28
|
-
|
29
|
+
expect(uploaded_file.id).to match %r{\Aasset/[0-9a-f]+}
|
30
|
+
end
|
29
31
|
end
|
30
32
|
|
33
|
+
describe "for shrine derivatives" do
|
34
|
+
let(:image_path) { Kithe::Engine.root.join("spec/test_support/images/1x1_pixel.jpg") }
|
35
|
+
|
36
|
+
let(:uploader) do
|
37
|
+
test_uploader do
|
38
|
+
plugin :kithe_storage_location
|
39
|
+
end
|
40
|
+
end
|
31
41
|
|
42
|
+
it "uses good path for derivative" do
|
43
|
+
uploaded_file = uploader.upload(fakeio("foo.jpg"), derivative: :fixed, record: OpenStruct.new(id: "81060886-4f93-42e7-ace7-ab51399f4808"), name: :file)
|
44
|
+
|
45
|
+
expect(uploaded_file.id).to match %r{\A81060886-4f93-42e7-ace7-ab51399f4808/fixed/[0-9a-f]+}
|
46
|
+
end
|
47
|
+
|
48
|
+
it "raises with no record" do
|
49
|
+
expect {
|
50
|
+
uploader.upload(fakeio("foo.jpg"), record: nil, derivative: :fixed, name: :file)
|
51
|
+
}.to raise_error(TypeError)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "raises with record with no id" do
|
55
|
+
expect {
|
56
|
+
uploader.upload(fakeio("foo.jpg"), record: OpenStruct.new, derivative: :fixed, name: :file)
|
57
|
+
}.to raise_error(TypeError)
|
58
|
+
end
|
59
|
+
end
|
32
60
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -132,13 +132,14 @@ end
|
|
132
132
|
require 'sane_patch'
|
133
133
|
SanePatch.patch("shrine", "< 3.2.2") do
|
134
134
|
require 'shrine/storage/memory'
|
135
|
+
|
135
136
|
class Shrine::Storage::Memory
|
136
|
-
def open(id,
|
137
|
-
|
138
|
-
|
137
|
+
def open(id, **)
|
138
|
+
io = StringIO.new(store.fetch(id))
|
139
|
+
io.set_encoding(io.string.encoding) # Ruby 2.7.0 – https://bugs.ruby-lang.org/issues/16497
|
140
|
+
io
|
141
|
+
rescue KeyError
|
142
|
+
raise Shrine::FileNotFound, "file #{id.inspect} not found on storage"
|
139
143
|
end
|
140
144
|
end
|
141
145
|
end
|
142
|
-
|
143
|
-
|
144
|
-
|
Binary file
|
@@ -59,8 +59,9 @@ require "stringio"
|
|
59
59
|
class FakeIO
|
60
60
|
attr_reader :original_filename, :content_type
|
61
61
|
|
62
|
-
def initialize(content, filename: nil, content_type: nil)
|
62
|
+
def initialize(content, filename: nil, content_type: nil, encoding: "BINARY")
|
63
63
|
@io = StringIO.new(content)
|
64
|
+
@io.set_encoding(encoding, encoding) # weird ruby workaround
|
64
65
|
@original_filename = filename
|
65
66
|
@content_type = content_type
|
66
67
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kithe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0.pre.
|
4
|
+
version: 2.0.0.pre.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Rochkind
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-05-
|
11
|
+
date: 2020-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -272,20 +272,6 @@ dependencies:
|
|
272
272
|
- - ">="
|
273
273
|
- !ruby/object:Gem::Version
|
274
274
|
version: '0'
|
275
|
-
- !ruby/object:Gem::Dependency
|
276
|
-
name: shrine-memory
|
277
|
-
requirement: !ruby/object:Gem::Requirement
|
278
|
-
requirements:
|
279
|
-
- - ">="
|
280
|
-
- !ruby/object:Gem::Version
|
281
|
-
version: '0'
|
282
|
-
type: :development
|
283
|
-
prerelease: false
|
284
|
-
version_requirements: !ruby/object:Gem::Requirement
|
285
|
-
requirements:
|
286
|
-
- - ">="
|
287
|
-
- !ruby/object:Gem::Version
|
288
|
-
version: '0'
|
289
275
|
- !ruby/object:Gem::Dependency
|
290
276
|
name: webmock
|
291
277
|
requirement: !ruby/object:Gem::Requirement
|
@@ -341,10 +327,9 @@ files:
|
|
341
327
|
- app/models/kithe/asset.rb
|
342
328
|
- app/models/kithe/asset/derivative_creator.rb
|
343
329
|
- app/models/kithe/asset/derivative_definition.rb
|
344
|
-
- app/models/kithe/asset/
|
330
|
+
- app/models/kithe/asset/set_shrine_uploader.rb
|
345
331
|
- app/models/kithe/collection.rb
|
346
332
|
- app/models/kithe/config_base.rb
|
347
|
-
- app/models/kithe/derivative.rb
|
348
333
|
- app/models/kithe/mediainfo_analyzer.rb
|
349
334
|
- app/models/kithe/model.rb
|
350
335
|
- app/models/kithe/model_contains.rb
|
@@ -355,7 +340,6 @@ files:
|
|
355
340
|
- app/simple_form_enhancements/kithe/form_builder.rb
|
356
341
|
- app/simple_form_enhancements/kithe/repeatable_input_generator.rb
|
357
342
|
- app/uploaders/kithe/asset_uploader.rb
|
358
|
-
- app/uploaders/kithe/derivative_uploader.rb
|
359
343
|
- app/validators/array_inclusion_validator.rb
|
360
344
|
- config/locales/en.yml
|
361
345
|
- config/routes.rb
|
@@ -375,7 +359,13 @@ files:
|
|
375
359
|
- lib/kithe/sti_preload.rb
|
376
360
|
- lib/kithe/version.rb
|
377
361
|
- lib/shrine/plugins/kithe_accept_remote_url.rb
|
362
|
+
- lib/shrine/plugins/kithe_checksum_signatures.rb
|
363
|
+
- lib/shrine/plugins/kithe_controllable_backgrounding.rb
|
364
|
+
- lib/shrine/plugins/kithe_derivative_definitions.rb
|
365
|
+
- lib/shrine/plugins/kithe_derivatives.rb
|
366
|
+
- lib/shrine/plugins/kithe_determine_mime_type.rb
|
378
367
|
- lib/shrine/plugins/kithe_multi_cache.rb
|
368
|
+
- lib/shrine/plugins/kithe_persisted_derivatives.rb
|
379
369
|
- lib/shrine/plugins/kithe_promotion_callbacks.rb
|
380
370
|
- lib/shrine/plugins/kithe_promotion_directives.rb
|
381
371
|
- lib/shrine/plugins/kithe_storage_location.rb
|
@@ -448,19 +438,23 @@ files:
|
|
448
438
|
- spec/indexing/indexable_spec.rb
|
449
439
|
- spec/indexing/indexer_spec.rb
|
450
440
|
- spec/indexing/obj_extract_spec.rb
|
451
|
-
- spec/models/kithe/asset/
|
441
|
+
- spec/models/kithe/asset/asset_derivatives_spec.rb
|
452
442
|
- spec/models/kithe/asset/asset_promotion_hooks_spec.rb
|
443
|
+
- spec/models/kithe/asset/set_shrine_uploader_spec.rb
|
453
444
|
- spec/models/kithe/asset_spec.rb
|
454
445
|
- spec/models/kithe/collection_spec.rb
|
455
446
|
- spec/models/kithe/config_spec.rb
|
456
|
-
- spec/models/kithe/derivative_spec.rb
|
457
447
|
- spec/models/kithe/mediainfo_analyzer_spec.rb
|
458
448
|
- spec/models/kithe/model_spec.rb
|
459
449
|
- spec/models/kithe/parameters_spec.rb
|
460
450
|
- spec/models/kithe/representatives_spec.rb
|
461
451
|
- spec/models/kithe/work_spec.rb
|
462
452
|
- spec/rails_helper.rb
|
453
|
+
- spec/shrine/kithe_accept_remote_url_spec.rb
|
454
|
+
- spec/shrine/kithe_checksum_signatures_spec.rb
|
455
|
+
- spec/shrine/kithe_derivative_definitions_spec.rb
|
463
456
|
- spec/shrine/kithe_multi_cache_spec.rb
|
457
|
+
- spec/shrine/kithe_persisted_derivatives_spec.rb
|
464
458
|
- spec/shrine/kithe_storage_location_spec.rb
|
465
459
|
- spec/simple_form_enhancements/repeatable_input_generator_spec.rb
|
466
460
|
- spec/spec_helper.rb
|
@@ -468,6 +462,7 @@ files:
|
|
468
462
|
- spec/test_support/audio/ice_cubes.mp3
|
469
463
|
- spec/test_support/images/1x1_pixel.jpg
|
470
464
|
- spec/test_support/images/2x2_pixel.jpg
|
465
|
+
- spec/test_support/images/3x3_pixel.jpg
|
471
466
|
- spec/test_support/images/photo_800x586.jpg
|
472
467
|
- spec/test_support/shrine_spec_support.rb
|
473
468
|
- spec/test_support/temporary_class_for_specs.rb
|
@@ -561,23 +556,28 @@ test_files:
|
|
561
556
|
- spec/derivative_transformers/ffmpeg_transformer_spec.rb
|
562
557
|
- spec/models/kithe/collection_spec.rb
|
563
558
|
- spec/models/kithe/config_spec.rb
|
559
|
+
- spec/models/kithe/asset/asset_derivatives_spec.rb
|
564
560
|
- spec/models/kithe/asset/asset_promotion_hooks_spec.rb
|
565
|
-
- spec/models/kithe/asset/
|
561
|
+
- spec/models/kithe/asset/set_shrine_uploader_spec.rb
|
566
562
|
- spec/models/kithe/representatives_spec.rb
|
567
563
|
- spec/models/kithe/work_spec.rb
|
568
564
|
- spec/models/kithe/mediainfo_analyzer_spec.rb
|
569
565
|
- spec/models/kithe/parameters_spec.rb
|
570
|
-
- spec/models/kithe/derivative_spec.rb
|
571
566
|
- spec/models/kithe/asset_spec.rb
|
572
567
|
- spec/models/kithe/model_spec.rb
|
573
568
|
- spec/shrine/kithe_multi_cache_spec.rb
|
574
569
|
- spec/shrine/kithe_storage_location_spec.rb
|
570
|
+
- spec/shrine/kithe_persisted_derivatives_spec.rb
|
571
|
+
- spec/shrine/kithe_derivative_definitions_spec.rb
|
572
|
+
- spec/shrine/kithe_accept_remote_url_spec.rb
|
573
|
+
- spec/shrine/kithe_checksum_signatures_spec.rb
|
575
574
|
- spec/factories/kithe_works.rb
|
576
575
|
- spec/factories/kithe_collections.rb
|
577
576
|
- spec/factories/kithe_assets.rb
|
578
577
|
- spec/test_support/temporary_class_for_specs.rb
|
579
578
|
- spec/test_support/shrine_spec_support.rb
|
580
579
|
- spec/test_support/images/2x2_pixel.jpg
|
580
|
+
- spec/test_support/images/3x3_pixel.jpg
|
581
581
|
- spec/test_support/images/1x1_pixel.jpg
|
582
582
|
- spec/test_support/images/photo_800x586.jpg
|
583
583
|
- spec/test_support/audio/README.md
|
@@ -1,119 +0,0 @@
|
|
1
|
-
# A service object to add an IO stream as a derivative with a certain key, to a asset.
|
2
|
-
# Adds a Derivative database object for such. This class is normally only used from
|
3
|
-
# Asset#update_derivative, it's a helper object, you aren't expected to use it independently.
|
4
|
-
#
|
5
|
-
# This would be very straightforward if it weren't for taking account of a couple concurrency race
|
6
|
-
# conditions involving data integrity:
|
7
|
-
#
|
8
|
-
# 1. There should be only one derivative for a given asset/key pair. This is enforced by
|
9
|
-
# a DB constraint. If the record already exists, we want to update the current record,
|
10
|
-
# otherwise add a new one. We want to do this in a race-condition safe way, with possibly
|
11
|
-
# multiple processes editing db.
|
12
|
-
#
|
13
|
-
# 2. The DB should at no point in time contain a derivative generated for an _old_ version
|
14
|
-
# of the asset. If an asset#file is changed, it's existing derivatives need to be deleted,
|
15
|
-
# and this needs to happen in a race-condition safe way when something may be trying to
|
16
|
-
# add a derivative concurrently.
|
17
|
-
#
|
18
|
-
# I believe we have solved those challenges, but it leads to a bit tricky code. We use
|
19
|
-
# a kind of "optimistic" approach to (1) (try to insert, if you get a uniqueness violation
|
20
|
-
# try to find and use the record that's already there). And a "pessimistic" approach to (2),
|
21
|
-
# where we actually briefly take out a DB pessimistic lock to make sure the asset#file hasn't
|
22
|
-
# changed, and can't until we're done updating. (using sha512 as a marker, which is why you
|
23
|
-
# can't add an asset until it has a sha512 in it's metadata, usually post-promotion).
|
24
|
-
#
|
25
|
-
# If we made a given Asset objects's file bytestream immutable, this would all be a lot simpler;
|
26
|
-
# we wouldn't need to worry about (2) at all, and maybe not even (1). We might consider that, but
|
27
|
-
# for now we're tackling the hard way.
|
28
|
-
#
|
29
|
-
class Kithe::Asset::DerivativeUpdater
|
30
|
-
attr_reader :asset, :key, :io, :storage_key, :metadata, :max_optimistic_tries
|
31
|
-
|
32
|
-
def initialize(asset, key, io, storage_key: :kithe_derivatives, metadata: {})
|
33
|
-
@asset = asset
|
34
|
-
@key = key
|
35
|
-
@io = io
|
36
|
-
@storage_key = storage_key
|
37
|
-
@metadata = metadata
|
38
|
-
|
39
|
-
@max_optimistic_tries = 3
|
40
|
-
|
41
|
-
unless asset_has_persisted_sha512?
|
42
|
-
raise ArgumentError.new("Can not safely add derivative to an asset without a persisted sha512 value")
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def update
|
47
|
-
deriv = Kithe::Derivative.new(key: key.to_s, asset: asset)
|
48
|
-
|
49
|
-
# skip cache phase, right to specified storage, but with metadata extraction.
|
50
|
-
uploader = deriv.file_attacher.shrine_class.new(storage_key)
|
51
|
-
|
52
|
-
# add our derivative key to context when uploading, so Kithe::DerivativeUploader can
|
53
|
-
# use it if needed.
|
54
|
-
uploaded_file = uploader.upload(io, record: deriv, metadata: metadata, kithe_derivative_key: key)
|
55
|
-
optimistically_save_derivative(uploaded_file: uploaded_file, derivative: deriv)
|
56
|
-
end
|
57
|
-
|
58
|
-
# Attaches UploadedFile to Derivative and tries to save it -- if we get a
|
59
|
-
# unique constraint violation because a Derivative for that asset/key already existed,
|
60
|
-
# we fetch that alredy existing one from the db and update it's actual bytestream.
|
61
|
-
#
|
62
|
-
# This method calls itself recursively to do that. Gives up after max_optimistic_tries,
|
63
|
-
# at which point it'll just raise the constraint violation exception.
|
64
|
-
def optimistically_save_derivative(uploaded_file:, derivative:, tries: 0)
|
65
|
-
derivative.file_attacher.change(uploaded_file)
|
66
|
-
save_deriv_ensuring_unchanged_asset(derivative)
|
67
|
-
rescue ActiveRecord::RecordNotUnique => e
|
68
|
-
if tries < max_optimistic_tries
|
69
|
-
# find the one that's already there, try to attach our new file
|
70
|
-
# to that one
|
71
|
-
derivative = Kithe::Derivative.where(key: key.to_s, asset: asset).first || derivative
|
72
|
-
optimistically_save_derivative(uploaded_file: uploaded_file, derivative: derivative, tries: tries + 1)
|
73
|
-
else
|
74
|
-
uploaded_file.delete if uploaded_file
|
75
|
-
raise e
|
76
|
-
end
|
77
|
-
rescue StandardError => e
|
78
|
-
# aggressively clean up our file on errors!
|
79
|
-
uploaded_file.delete if uploaded_file
|
80
|
-
raise e
|
81
|
-
end
|
82
|
-
|
83
|
-
# Save a Derivative model with some fancy DB footwork to ensure at the time
|
84
|
-
# we save it, the original asset file it is based on is still in db unchanged,
|
85
|
-
# in a concurrency-safe way.
|
86
|
-
#
|
87
|
-
# We re-fetch to ensure asset still exists, with sha512 we expect. (kithe model ensures sha512
|
88
|
-
# exists in shrine metadata). With a pessmistic lock in a transaction. This ensures that at
|
89
|
-
# the point we save the new derivative, the db is still in a state where the original file
|
90
|
-
# the derivative relates to is still in the db.
|
91
|
-
#
|
92
|
-
# Can raise a ActiveRecord::RecordNotUnique, if derivative unique constraint is violated,
|
93
|
-
# that is handled above here.
|
94
|
-
def save_deriv_ensuring_unchanged_asset(deriv)
|
95
|
-
# fancy throw/catch keep our abort rescue from being in the transaction
|
96
|
-
catch(:kithe_unchanged_abort) do
|
97
|
-
Kithe::Asset.transaction do
|
98
|
-
# the file we're trying to add a derivative to doesn't exist anymore, forget it
|
99
|
-
unless asset.acquire_lock_on_sha
|
100
|
-
throw :kithe_unchanged_abort
|
101
|
-
end
|
102
|
-
|
103
|
-
deriv.save!
|
104
|
-
return deriv
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
# If we made it here, we've aborted
|
109
|
-
deriv.file.delete
|
110
|
-
return nil
|
111
|
-
end
|
112
|
-
|
113
|
-
def asset_has_persisted_sha512?
|
114
|
-
asset.persisted? && asset.sha512.present? &&
|
115
|
-
!( asset.file_data_changed? &&
|
116
|
-
asset.file_data_change.first.try(:dig, "metadata", "sha512") !=
|
117
|
-
asset.file_data_change.second.try(:dig, "metadata", "sha512"))
|
118
|
-
end
|
119
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
module Kithe
|
2
|
-
|
3
|
-
# Derivatives by default will be stored in Shrine storage :kithe_derivatives, so
|
4
|
-
# that should be registered in your app.
|
5
|
-
#
|
6
|
-
# Only one deriv can exist for a given asset_id/key pair, enforced by db constraint.
|
7
|
-
class Derivative < ApplicationRecord
|
8
|
-
# the fk is to kithe_models STI table, but we only intend for assets
|
9
|
-
belongs_to :asset, class_name: "Kithe::Asset"
|
10
|
-
|
11
|
-
include Kithe::DerivativeUploader::Attachment.new(:file, store: :kithe_derivatives)
|
12
|
-
|
13
|
-
delegate :content_type, :size, :height, :width, :url, to: :file, allow_nil: true
|
14
|
-
end
|
15
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
require 'mini_mime'
|
2
|
-
|
3
|
-
module Kithe
|
4
|
-
# The derivative uploader doesn't have to do too much, we don't even use
|
5
|
-
# promotion for derivatives, just writing directly to a storage.
|
6
|
-
#
|
7
|
-
# But it needs activerecord integration, and limited metadata automatic extraction.
|
8
|
-
class DerivativeUploader < Shrine
|
9
|
-
plugin :activerecord
|
10
|
-
|
11
|
-
plugin :determine_mime_type, analyzer: :marcel
|
12
|
-
|
13
|
-
# ignore error, often from storing a non-image file which can't have dimensions
|
14
|
-
# extracted. behavior consistent with shrine 2.x.
|
15
|
-
plugin :store_dimensions, on_error: :ignore
|
16
|
-
|
17
|
-
# Useful in case consumers want it, and doesn't harm anything to be available.
|
18
|
-
# https://github.com/shrinerb/shrine/blob/master/doc/plugins/rack_response.md
|
19
|
-
plugin :rack_response
|
20
|
-
|
21
|
-
# should this be in a plugin? location in file system based on original asset
|
22
|
-
# id and derivative key, as well as unique random file id from shrine.
|
23
|
-
def generate_location(io, context)
|
24
|
-
# assumes we're only used with Derivative model, that has an asset_id and key
|
25
|
-
asset_id = context[:record].asset_id
|
26
|
-
key = context[:record].key
|
27
|
-
original = super
|
28
|
-
[asset_id, key, original].compact.join("/")
|
29
|
-
end
|
30
|
-
|
31
|
-
|
32
|
-
# Override to fix "filename" metadata to be something reasonable, regardless
|
33
|
-
# of what if anything was the filename of the IO being attached. shrine S3 will
|
34
|
-
# insist on setting a default content-disposition with this filename.
|
35
|
-
def extract_metadata(io, context = {})
|
36
|
-
result = super
|
37
|
-
|
38
|
-
if context[:kithe_derivative_key] &&
|
39
|
-
context[:record]
|
40
|
-
extension = MiniMime.lookup_by_content_type(result["mime_type"] || "")&.extension
|
41
|
-
result["filename"] = "#{context[:record].asset.friendlier_id}_#{context[:kithe_derivative_key]}.#{extension}"
|
42
|
-
result["kithe_derivative_key"] = context[:kithe_derivative_key]
|
43
|
-
end
|
44
|
-
|
45
|
-
return result
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
@@ -1,320 +0,0 @@
|
|
1
|
-
# Make a new test file cause it's a buncha func
|
2
|
-
require 'rails_helper'
|
3
|
-
|
4
|
-
# Not sure how to get our
|
5
|
-
describe "Kithe::Asset derivative definitions", queue_adapter: :test do
|
6
|
-
let(:a_jpg_deriv_file) { Kithe::Engine.root.join("spec/test_support/images/2x2_pixel.jpg") }
|
7
|
-
|
8
|
-
temporary_class("TestAssetSubclass") do
|
9
|
-
deriv_src_path = a_jpg_deriv_file
|
10
|
-
Class.new(Kithe::Asset) do
|
11
|
-
define_derivative(:some_data) do |original_file|
|
12
|
-
StringIO.new("some one data")
|
13
|
-
end
|
14
|
-
|
15
|
-
define_derivative(:a_jpg) do |original_file|
|
16
|
-
FileUtils.cp(deriv_src_path,
|
17
|
-
Kithe::Engine.root.join("spec/test_support/images/2x2_pixel-TEMP.jpg"))
|
18
|
-
|
19
|
-
File.open(Kithe::Engine.root.join("spec/test_support/images/2x2_pixel-TEMP.jpg"))
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
let(:asset) do
|
25
|
-
TestAssetSubclass.create(title: "test",
|
26
|
-
file: File.open(Kithe::Engine.root.join("spec/test_support/images/1x1_pixel.jpg"))
|
27
|
-
).tap do |a|
|
28
|
-
# We want to promote without create_derivatives being automatically called
|
29
|
-
# as usual, so we can test create_derivatives manually.
|
30
|
-
a.file_attacher.set_promotion_directives(create_derivatives: false)
|
31
|
-
a.promote
|
32
|
-
|
33
|
-
# Precondition assumptions for our test setup to be valid
|
34
|
-
expect(a.file_attacher.stored?).to be(true)
|
35
|
-
expect(a.derivatives).to be_empty
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
it "builds derivatives" do
|
40
|
-
asset.create_derivatives
|
41
|
-
|
42
|
-
one_deriv = asset.derivatives.find { |d| d.key == "some_data" }
|
43
|
-
expect(one_deriv).to be_present
|
44
|
-
expect(one_deriv.file.read).to eq("some one data")
|
45
|
-
|
46
|
-
jpg_deriv = asset.derivatives.find {|d| d.key == "a_jpg"}
|
47
|
-
expect(jpg_deriv.file.read).to eq(File.read(a_jpg_deriv_file, encoding: "BINARY"))
|
48
|
-
end
|
49
|
-
|
50
|
-
it "sets #derivatives_created?" do
|
51
|
-
expect(asset.derivatives_created?).to be(false)
|
52
|
-
asset.create_derivatives
|
53
|
-
asset.reload
|
54
|
-
expect(asset.derivatives_created?).to be(true)
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
describe "Original deleted before derivatives can be created", queue_adapter: :inline do
|
59
|
-
let(:short_lived_asset) do
|
60
|
-
TestAssetSubclass.create!(title: "test",
|
61
|
-
file: File.open(Kithe::Engine.root.join("spec/test_support/images/1x1_pixel.jpg")))
|
62
|
-
end
|
63
|
-
it """catches the ActiveJob::DeserializationError
|
64
|
-
if the asset is no longer in the database
|
65
|
-
once the derivative creation job starts up.""" do
|
66
|
-
id_to_delete = short_lived_asset.id
|
67
|
-
Kithe::Derivative.where(asset_id: id_to_delete).delete_all
|
68
|
-
Kithe::Model.where(id: id_to_delete).delete_all
|
69
|
-
|
70
|
-
# short_lived_asset is no longer in the DB.
|
71
|
-
# Let's try and create derivatives for it:
|
72
|
-
expect do
|
73
|
-
Kithe::CreateDerivativesJob.perform_later(short_lived_asset)
|
74
|
-
end.not_to raise_error
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
describe "under normal operation", queue_adapter: :inline do
|
79
|
-
let(:asset) do
|
80
|
-
TestAssetSubclass.create!(title: "test",
|
81
|
-
file: File.open(Kithe::Engine.root.join("spec/test_support/images/1x1_pixel.jpg")))
|
82
|
-
end
|
83
|
-
it "automatically creates derivatives" do
|
84
|
-
expect(asset.derivatives.count).to eq(2)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
it "extracts limited metadata from derivative" do
|
89
|
-
asset.create_derivatives
|
90
|
-
|
91
|
-
jpg_deriv = asset.derivatives.find {|d| d.key == "a_jpg"}
|
92
|
-
expect(jpg_deriv.size).to eq(File.size(Kithe::Engine.root.join("spec/test_support/images/2x2_pixel.jpg")))
|
93
|
-
expect(jpg_deriv.width).to eq(2)
|
94
|
-
expect(jpg_deriv.height).to eq(2)
|
95
|
-
expect(jpg_deriv.content_type).to eq("image/jpeg")
|
96
|
-
end
|
97
|
-
|
98
|
-
it "deletes derivative file returned by block" do
|
99
|
-
asset.create_derivatives
|
100
|
-
|
101
|
-
expect(File.exist?(Kithe::Engine.root.join("spec/test_support/images/2x2_pixel-TEMP.jpg"))).not_to be(true)
|
102
|
-
end
|
103
|
-
|
104
|
-
it "by default saves in :kithe_derivatives storage" do
|
105
|
-
asset.create_derivatives
|
106
|
-
|
107
|
-
jpg_deriv = asset.derivatives.find {|d| d.key == "a_jpg"}
|
108
|
-
expect(jpg_deriv.file.storage_key).to eq(:kithe_derivatives)
|
109
|
-
end
|
110
|
-
|
111
|
-
|
112
|
-
describe "block arguments" do
|
113
|
-
let(:monitoring_proc) do
|
114
|
-
proc do |original_file, record:|
|
115
|
-
expect(original_file.kind_of?(File) || original_file.kind_of?(Tempfile)).to be(true)
|
116
|
-
expect(original_file.path).to be_present
|
117
|
-
expect(original_file.read).to eq(asset.file.read)
|
118
|
-
|
119
|
-
expect(record).to eq(asset)
|
120
|
-
|
121
|
-
nil
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
temporary_class("TestAssetSubclass") do
|
126
|
-
our_proc = monitoring_proc
|
127
|
-
Class.new(Kithe::Asset) do
|
128
|
-
define_derivative(:some_data, &our_proc)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
it "as expected" do
|
133
|
-
expect(monitoring_proc).to receive(:call).and_call_original
|
134
|
-
|
135
|
-
asset.create_derivatives
|
136
|
-
expect(asset.derivatives.length).to eq(0)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
describe "custom storage_key" do
|
141
|
-
temporary_class("TestAssetSubclass") do
|
142
|
-
Class.new(Kithe::Asset) do
|
143
|
-
define_derivative(:some_data, storage_key: :store) do |original_file|
|
144
|
-
StringIO.new("some one data")
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
it "saves appropriately" do
|
149
|
-
asset.create_derivatives
|
150
|
-
|
151
|
-
deriv = asset.derivatives.first
|
152
|
-
|
153
|
-
expect(deriv).to be_present
|
154
|
-
expect(deriv.file.storage_key).to eq(:store)
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
describe "default_create false" do
|
159
|
-
let(:monitoring_proc) { proc { |asset| } }
|
160
|
-
|
161
|
-
temporary_class("TestAssetSubclass") do
|
162
|
-
p = monitoring_proc
|
163
|
-
Class.new(Kithe::Asset) do
|
164
|
-
define_derivative(:some_data, default_create: false, &p)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
it "is not run automatically" do
|
169
|
-
expect(monitoring_proc).not_to receive(:call)
|
170
|
-
asset.create_derivatives
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
describe "only/except" do
|
175
|
-
let(:monitoring_proc1) { proc { |asset| StringIO.new("one") } }
|
176
|
-
let(:monitoring_proc2) { proc { |asset| StringIO.new("two") } }
|
177
|
-
let(:monitoring_proc3) { proc { |asset| StringIO.new("three") } }
|
178
|
-
|
179
|
-
temporary_class("TestAssetSubclass") do
|
180
|
-
p1, p2, p3 = monitoring_proc1, monitoring_proc2, monitoring_proc3
|
181
|
-
Class.new(Kithe::Asset) do
|
182
|
-
define_derivative(:one, default_create: false, &p1)
|
183
|
-
define_derivative(:two, &p2)
|
184
|
-
define_derivative(:three, &p3)
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
it "can call with only" do
|
189
|
-
expect(monitoring_proc1).to receive(:call).and_call_original
|
190
|
-
expect(monitoring_proc2).to receive(:call).and_call_original
|
191
|
-
expect(monitoring_proc3).not_to receive(:call)
|
192
|
-
|
193
|
-
asset.create_derivatives(only: [:one, :two])
|
194
|
-
|
195
|
-
expect(asset.derivatives.collect(&:key)).to eq(["one", "two"])
|
196
|
-
end
|
197
|
-
|
198
|
-
it "can call with except" do
|
199
|
-
expect(monitoring_proc1).not_to receive(:call)
|
200
|
-
expect(monitoring_proc2).to receive(:call).and_call_original
|
201
|
-
expect(monitoring_proc3).not_to receive(:call)
|
202
|
-
|
203
|
-
asset.create_derivatives(except: [:three])
|
204
|
-
|
205
|
-
expect(asset.derivatives.collect(&:key)).to eq(["two"])
|
206
|
-
end
|
207
|
-
|
208
|
-
it "can call with only and except" do
|
209
|
-
expect(monitoring_proc1).to receive(:call).and_call_original
|
210
|
-
expect(monitoring_proc2).not_to receive(:call)
|
211
|
-
expect(monitoring_proc3).not_to receive(:call)
|
212
|
-
|
213
|
-
asset.create_derivatives(only: [:one, :two], except: :two)
|
214
|
-
|
215
|
-
expect(asset.derivatives.collect(&:key)).to eq(["one"])
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
describe "content_type filters" do
|
220
|
-
temporary_class("TestAssetSubclass") do
|
221
|
-
Class.new(Kithe::Asset) do
|
222
|
-
define_derivative(:never_called, content_type: "nothing/nothing") { |o| StringIO.new("never") }
|
223
|
-
define_derivative(:gated_positive, content_type: "image/jpeg") { |o| StringIO.new("gated positive") }
|
224
|
-
define_derivative(:gated_positive_main_type, content_type: "image") { |o| StringIO.new("gated positive") }
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
it "does not call if content type does not match" do
|
229
|
-
asset.create_derivatives
|
230
|
-
expect(asset.derivatives.collect(&:key)).not_to include("never_called")
|
231
|
-
end
|
232
|
-
|
233
|
-
it "calls for exact content type match" do
|
234
|
-
asset.create_derivatives
|
235
|
-
expect(asset.derivatives.collect(&:key)).to include("gated_positive")
|
236
|
-
end
|
237
|
-
|
238
|
-
it "calls for main content type match" do
|
239
|
-
asset.create_derivatives
|
240
|
-
expect(asset.derivatives.collect(&:key)).to include("gated_positive_main_type")
|
241
|
-
end
|
242
|
-
|
243
|
-
describe "as array" do
|
244
|
-
temporary_class("TestAssetSubclass") do
|
245
|
-
Class.new(Kithe::Asset) do
|
246
|
-
define_derivative(:never_called, content_type: ["nothing/nothing", "also/nothing"]) { |o| StringIO.new("never") }
|
247
|
-
define_derivative(:gated_positive, content_type: ["image/jpeg", "something/else"]) { |o| StringIO.new("gated positive") }
|
248
|
-
end
|
249
|
-
end
|
250
|
-
it "calls for one match" do
|
251
|
-
asset.create_derivatives
|
252
|
-
expect(asset.derivatives.collect(&:key)).to eq(["gated_positive"])
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
describe "conflicting types" do
|
257
|
-
let(:unfiltered) { proc { |asset| StringIO.new("unfiltered") } }
|
258
|
-
let(:image) { proc { |asset| StringIO.new("image") } }
|
259
|
-
let(:image_jpeg) { proc { |asset| StringIO.new("image/jpeg") } }
|
260
|
-
|
261
|
-
|
262
|
-
temporary_class("TestAssetSubclass") do
|
263
|
-
u, i, ij = unfiltered, image, image_jpeg
|
264
|
-
Class.new(Kithe::Asset) do
|
265
|
-
define_derivative(:key, &u)
|
266
|
-
define_derivative(:key, content_type: "image/jpeg", &ij)
|
267
|
-
define_derivative(:key, content_type: "image", &i)
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
it "takes most specific" do
|
272
|
-
expect(unfiltered).not_to receive(:call)
|
273
|
-
expect(image).not_to receive(:call)
|
274
|
-
expect(image_jpeg).to receive(:call).and_call_original
|
275
|
-
|
276
|
-
asset.create_derivatives
|
277
|
-
expect(asset.derivatives.count).to eq(1)
|
278
|
-
|
279
|
-
deriv = asset.derivatives.first
|
280
|
-
expect(deriv.key).to eq("key")
|
281
|
-
expect(deriv.file.read). to eq("image/jpeg")
|
282
|
-
end
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
describe "lazy creation" do
|
287
|
-
before do
|
288
|
-
asset.class.derivative_definitions.collect(&:key).each do |key|
|
289
|
-
asset.update_derivative(key, StringIO.new("#{key} original"))
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
it "does not re-create" do
|
294
|
-
derivatives_pre_creation = asset.derivatives.collect(&:attributes)
|
295
|
-
|
296
|
-
asset.create_derivatives(lazy: true)
|
297
|
-
derivatives_post_creation = asset.derivatives.reload.collect(&:attributes)
|
298
|
-
|
299
|
-
expect(derivatives_post_creation).to eq(derivatives_pre_creation)
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
describe "#remove_derivative_definition!" do
|
304
|
-
it "can remove by string" do
|
305
|
-
original_keys = TestAssetSubclass.defined_derivative_keys
|
306
|
-
TestAssetSubclass.remove_derivative_definition!(original_keys.first.to_s)
|
307
|
-
expect(TestAssetSubclass.defined_derivative_keys).to eq(original_keys.slice(1..original_keys.length))
|
308
|
-
end
|
309
|
-
it "can remove by symbol" do
|
310
|
-
original_keys = TestAssetSubclass.defined_derivative_keys
|
311
|
-
TestAssetSubclass.remove_derivative_definition!(original_keys.first.to_sym)
|
312
|
-
expect(TestAssetSubclass.defined_derivative_keys).to eq(original_keys.slice(1..original_keys.length))
|
313
|
-
end
|
314
|
-
it "can remove multiple args" do
|
315
|
-
original_keys = TestAssetSubclass.defined_derivative_keys
|
316
|
-
TestAssetSubclass.remove_derivative_definition!(*original_keys)
|
317
|
-
expect(TestAssetSubclass.defined_derivative_keys).to eq([])
|
318
|
-
end
|
319
|
-
end
|
320
|
-
end
|