activestorage 6.1.7.2 → 7.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activestorage might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +126 -291
- data/MIT-LICENSE +1 -1
- data/README.md +25 -11
- data/app/assets/javascripts/activestorage.esm.js +844 -0
- data/app/assets/javascripts/activestorage.js +257 -376
- data/app/controllers/active_storage/base_controller.rb +1 -10
- data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -4
- data/app/controllers/active_storage/blobs/redirect_controller.rb +6 -4
- data/app/controllers/active_storage/representations/base_controller.rb +5 -1
- data/app/controllers/active_storage/representations/proxy_controller.rb +6 -4
- data/app/controllers/active_storage/representations/redirect_controller.rb +6 -4
- data/app/controllers/concerns/active_storage/set_blob.rb +6 -2
- data/app/controllers/concerns/active_storage/set_current.rb +3 -3
- data/app/controllers/concerns/active_storage/streaming.rb +65 -0
- data/app/javascript/activestorage/ujs.js +1 -1
- data/app/models/active_storage/attachment.rb +35 -2
- data/app/models/active_storage/blob/representable.rb +7 -5
- data/app/models/active_storage/blob.rb +26 -27
- data/app/models/active_storage/current.rb +12 -2
- data/app/models/active_storage/preview.rb +6 -4
- data/app/models/active_storage/record.rb +1 -1
- data/app/models/active_storage/variant.rb +6 -9
- data/app/models/active_storage/variant_record.rb +2 -0
- data/app/models/active_storage/variant_with_record.rb +9 -5
- data/app/models/active_storage/variation.rb +3 -3
- data/config/routes.rb +10 -10
- data/db/migrate/20170806125915_create_active_storage_tables.rb +11 -2
- data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +0 -4
- data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +1 -3
- data/lib/active_storage/analyzer/audio_analyzer.rb +65 -0
- data/lib/active_storage/analyzer/image_analyzer/image_magick.rb +39 -0
- data/lib/active_storage/analyzer/image_analyzer/vips.rb +49 -0
- data/lib/active_storage/analyzer/image_analyzer.rb +2 -30
- data/lib/active_storage/analyzer/video_analyzer.rb +26 -11
- data/lib/active_storage/analyzer.rb +8 -4
- data/lib/active_storage/attached/changes/create_many.rb +7 -3
- data/lib/active_storage/attached/changes/create_one.rb +1 -1
- data/lib/active_storage/attached/changes/create_one_of_many.rb +1 -1
- data/lib/active_storage/attached/changes/delete_many.rb +1 -1
- data/lib/active_storage/attached/changes/delete_one.rb +1 -1
- data/lib/active_storage/attached/changes/detach_many.rb +18 -0
- data/lib/active_storage/attached/changes/detach_one.rb +24 -0
- data/lib/active_storage/attached/changes/purge_many.rb +27 -0
- data/lib/active_storage/attached/changes/purge_one.rb +27 -0
- data/lib/active_storage/attached/changes.rb +7 -1
- data/lib/active_storage/attached/many.rb +27 -15
- data/lib/active_storage/attached/model.rb +31 -5
- data/lib/active_storage/attached/one.rb +32 -27
- data/lib/active_storage/downloader.rb +2 -2
- data/lib/active_storage/engine.rb +28 -16
- data/lib/active_storage/fixture_set.rb +76 -0
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/previewer/video_previewer.rb +0 -2
- data/lib/active_storage/previewer.rb +4 -4
- data/lib/active_storage/reflection.rb +12 -2
- data/lib/active_storage/service/azure_storage_service.rb +1 -1
- data/lib/active_storage/service/configurator.rb +1 -1
- data/lib/active_storage/service/disk_service.rb +13 -18
- data/lib/active_storage/service/gcs_service.rb +91 -7
- data/lib/active_storage/service/mirror_service.rb +1 -1
- data/lib/active_storage/service/registry.rb +1 -1
- data/lib/active_storage/service/s3_service.rb +4 -4
- data/lib/active_storage/service.rb +3 -3
- data/lib/active_storage/transformers/image_processing_transformer.rb +1 -66
- data/lib/active_storage/transformers/transformer.rb +1 -1
- data/lib/active_storage.rb +3 -292
- metadata +32 -24
- data/app/controllers/concerns/active_storage/set_headers.rb +0 -12
@@ -8,9 +8,9 @@ require "mini_mime"
|
|
8
8
|
# In case you do need to use this directly, it's instantiated using a hash of transformations where
|
9
9
|
# the key is the command and the value is the arguments. Example:
|
10
10
|
#
|
11
|
-
# ActiveStorage::Variation.new(resize_to_limit: [100, 100],
|
11
|
+
# ActiveStorage::Variation.new(resize_to_limit: [100, 100], colourspace: "b-w", rotate: "-90", saver: { trim: true })
|
12
12
|
#
|
13
|
-
# The options map directly to {ImageProcessing}[https://github.com/janko/image_processing] commands.
|
13
|
+
# The options map directly to {ImageProcessing}[https://github.com/janko-m/image_processing] commands.
|
14
14
|
class ActiveStorage::Variation
|
15
15
|
attr_reader :transformations
|
16
16
|
|
@@ -75,7 +75,7 @@ class ActiveStorage::Variation
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def digest
|
78
|
-
Digest::SHA1.base64digest Marshal.dump(transformations)
|
78
|
+
OpenSSL::Digest::SHA1.base64digest Marshal.dump(transformations)
|
79
79
|
end
|
80
80
|
|
81
81
|
private
|
data/config/routes.rb
CHANGED
@@ -16,11 +16,7 @@ Rails.application.routes.draw do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
direct :rails_representation do |representation, options|
|
19
|
-
|
20
|
-
variation_key = representation.variation.key
|
21
|
-
filename = representation.blob.filename
|
22
|
-
|
23
|
-
route_for(:rails_blob_representation, signed_blob_id, variation_key, filename, options)
|
19
|
+
route_for(ActiveStorage.resolve_model_to_route, representation, options)
|
24
20
|
end
|
25
21
|
|
26
22
|
resolve("ActiveStorage::Variant") { |variant, options| route_for(ActiveStorage.resolve_model_to_route, variant, options) }
|
@@ -28,22 +24,24 @@ Rails.application.routes.draw do
|
|
28
24
|
resolve("ActiveStorage::Preview") { |preview, options| route_for(ActiveStorage.resolve_model_to_route, preview, options) }
|
29
25
|
|
30
26
|
direct :rails_blob do |blob, options|
|
31
|
-
route_for(
|
27
|
+
route_for(ActiveStorage.resolve_model_to_route, blob, options)
|
32
28
|
end
|
33
29
|
|
34
30
|
resolve("ActiveStorage::Blob") { |blob, options| route_for(ActiveStorage.resolve_model_to_route, blob, options) }
|
35
31
|
resolve("ActiveStorage::Attachment") { |attachment, options| route_for(ActiveStorage.resolve_model_to_route, attachment.blob, options) }
|
36
32
|
|
37
33
|
direct :rails_storage_proxy do |model, options|
|
34
|
+
expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
|
35
|
+
|
38
36
|
if model.respond_to?(:signed_id)
|
39
37
|
route_for(
|
40
38
|
:rails_service_blob_proxy,
|
41
|
-
model.signed_id,
|
39
|
+
model.signed_id(expires_in: expires_in),
|
42
40
|
model.filename,
|
43
41
|
options
|
44
42
|
)
|
45
43
|
else
|
46
|
-
signed_blob_id = model.blob.signed_id
|
44
|
+
signed_blob_id = model.blob.signed_id(expires_in: expires_in)
|
47
45
|
variation_key = model.variation.key
|
48
46
|
filename = model.blob.filename
|
49
47
|
|
@@ -58,15 +56,17 @@ Rails.application.routes.draw do
|
|
58
56
|
end
|
59
57
|
|
60
58
|
direct :rails_storage_redirect do |model, options|
|
59
|
+
expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
|
60
|
+
|
61
61
|
if model.respond_to?(:signed_id)
|
62
62
|
route_for(
|
63
63
|
:rails_service_blob,
|
64
|
-
model.signed_id,
|
64
|
+
model.signed_id(expires_in: expires_in),
|
65
65
|
model.filename,
|
66
66
|
options
|
67
67
|
)
|
68
68
|
else
|
69
|
-
signed_blob_id = model.blob.signed_id
|
69
|
+
signed_blob_id = model.blob.signed_id(expires_in: expires_in)
|
70
70
|
variation_key = model.variation.key
|
71
71
|
filename = model.blob.filename
|
72
72
|
|
@@ -11,7 +11,12 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
|
11
11
|
t.string :service_name, null: false
|
12
12
|
t.bigint :byte_size, null: false
|
13
13
|
t.string :checksum, null: false
|
14
|
-
|
14
|
+
|
15
|
+
if connection.supports_datetime_with_precision?
|
16
|
+
t.datetime :created_at, precision: 6, null: false
|
17
|
+
else
|
18
|
+
t.datetime :created_at, null: false
|
19
|
+
end
|
15
20
|
|
16
21
|
t.index [ :key ], unique: true
|
17
22
|
end
|
@@ -21,7 +26,11 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
|
21
26
|
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
|
22
27
|
t.references :blob, null: false, type: foreign_key_type
|
23
28
|
|
24
|
-
|
29
|
+
if connection.supports_datetime_with_precision?
|
30
|
+
t.datetime :created_at, precision: 6, null: false
|
31
|
+
else
|
32
|
+
t.datetime :created_at, null: false
|
33
|
+
end
|
25
34
|
|
26
35
|
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
|
27
36
|
t.foreign_key :active_storage_blobs, column: :blob_id
|
@@ -1,7 +1,5 @@
|
|
1
1
|
class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
|
2
2
|
def up
|
3
|
-
return unless table_exists?(:active_storage_blobs)
|
4
|
-
|
5
3
|
unless column_exists?(:active_storage_blobs, :service_name)
|
6
4
|
add_column :active_storage_blobs, :service_name, :string
|
7
5
|
|
@@ -14,8 +12,6 @@ class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
|
|
14
12
|
end
|
15
13
|
|
16
14
|
def down
|
17
|
-
return unless table_exists?(:active_storage_blobs)
|
18
|
-
|
19
15
|
remove_column :active_storage_blobs, :service_name
|
20
16
|
end
|
21
17
|
end
|
@@ -1,9 +1,7 @@
|
|
1
1
|
class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
|
2
2
|
def change
|
3
|
-
return unless table_exists?(:active_storage_blobs)
|
4
|
-
|
5
3
|
# Use Active Record's configured type for primary key
|
6
|
-
create_table :active_storage_variant_records, id: primary_key_type
|
4
|
+
create_table :active_storage_variant_records, id: primary_key_type do |t|
|
7
5
|
t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type
|
8
6
|
t.string :variation_digest, null: false
|
9
7
|
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
# Extracts duration (seconds) and bit_rate (bits/s) from an audio blob.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# ActiveStorage::Analyzer::AudioAnalyzer.new(blob).metadata
|
9
|
+
# # => { duration: 5.0, bit_rate: 320340 }
|
10
|
+
#
|
11
|
+
# This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
|
12
|
+
class Analyzer::AudioAnalyzer < Analyzer
|
13
|
+
def self.accept?(blob)
|
14
|
+
blob.audio?
|
15
|
+
end
|
16
|
+
|
17
|
+
def metadata
|
18
|
+
{ duration: duration, bit_rate: bit_rate }.compact
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def duration
|
23
|
+
duration = audio_stream["duration"]
|
24
|
+
Float(duration) if duration
|
25
|
+
end
|
26
|
+
|
27
|
+
def bit_rate
|
28
|
+
bit_rate = audio_stream["bit_rate"]
|
29
|
+
Integer(bit_rate) if bit_rate
|
30
|
+
end
|
31
|
+
|
32
|
+
def audio_stream
|
33
|
+
@audio_stream ||= streams.detect { |stream| stream["codec_type"] == "audio" } || {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def streams
|
37
|
+
probe["streams"] || []
|
38
|
+
end
|
39
|
+
|
40
|
+
def probe
|
41
|
+
@probe ||= download_blob_to_tempfile { |file| probe_from(file) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def probe_from(file)
|
45
|
+
instrument(File.basename(ffprobe_path)) do
|
46
|
+
IO.popen([ ffprobe_path,
|
47
|
+
"-print_format", "json",
|
48
|
+
"-show_streams",
|
49
|
+
"-show_format",
|
50
|
+
"-v", "error",
|
51
|
+
file.path
|
52
|
+
]) do |output|
|
53
|
+
JSON.parse(output.read)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue Errno::ENOENT
|
57
|
+
logger.info "Skipping audio analysis because FFmpeg isn't installed"
|
58
|
+
{}
|
59
|
+
end
|
60
|
+
|
61
|
+
def ffprobe_path
|
62
|
+
ActiveStorage.paths[:ffprobe] || "ffprobe"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
# This analyzer relies on the third-party {MiniMagick}[https://github.com/minimagick/minimagick] gem. MiniMagick requires
|
5
|
+
# the {ImageMagick}[http://www.imagemagick.org] system library.
|
6
|
+
class Analyzer::ImageAnalyzer::ImageMagick < Analyzer::ImageAnalyzer
|
7
|
+
def self.accept?(blob)
|
8
|
+
super && ActiveStorage.variant_processor == :mini_magick
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def read_image
|
13
|
+
download_blob_to_tempfile do |file|
|
14
|
+
require "mini_magick"
|
15
|
+
|
16
|
+
image = instrument("mini_magick") do
|
17
|
+
MiniMagick::Image.new(file.path)
|
18
|
+
end
|
19
|
+
|
20
|
+
if image.valid?
|
21
|
+
yield image
|
22
|
+
else
|
23
|
+
logger.info "Skipping image analysis because ImageMagick doesn't support the file"
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
rescue LoadError
|
28
|
+
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
|
29
|
+
{}
|
30
|
+
rescue MiniMagick::Error => error
|
31
|
+
logger.error "Skipping image analysis due to an ImageMagick error: #{error.message}"
|
32
|
+
{}
|
33
|
+
end
|
34
|
+
|
35
|
+
def rotated_image?(image)
|
36
|
+
%w[ RightTop LeftBottom TopRight BottomLeft ].include?(image["%[orientation]"])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
# This analyzer relies on the third-party {ruby-vips}[https://github.com/libvips/ruby-vips] gem. Ruby-vips requires
|
5
|
+
# the {libvips}[https://libvips.github.io/libvips/] system library.
|
6
|
+
class Analyzer::ImageAnalyzer::Vips < Analyzer::ImageAnalyzer
|
7
|
+
def self.accept?(blob)
|
8
|
+
super && ActiveStorage.variant_processor == :vips
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def read_image
|
13
|
+
download_blob_to_tempfile do |file|
|
14
|
+
require "ruby-vips"
|
15
|
+
|
16
|
+
image = instrument("vips") do
|
17
|
+
::Vips::Image.new_from_file(file.path, access: :sequential)
|
18
|
+
end
|
19
|
+
|
20
|
+
if valid_image?(image)
|
21
|
+
yield image
|
22
|
+
else
|
23
|
+
logger.info "Skipping image analysis because Vips doesn't support the file"
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
rescue LoadError
|
28
|
+
logger.info "Skipping image analysis because the ruby-vips gem isn't installed"
|
29
|
+
{}
|
30
|
+
rescue ::Vips::Error => error
|
31
|
+
logger.error "Skipping image analysis due to an Vips error: #{error.message}"
|
32
|
+
{}
|
33
|
+
end
|
34
|
+
|
35
|
+
ROTATIONS = /Right-top|Left-bottom|Top-right|Bottom-left/
|
36
|
+
def rotated_image?(image)
|
37
|
+
ROTATIONS === image.get("exif-ifd0-Orientation")
|
38
|
+
rescue ::Vips::Error
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid_image?(image)
|
43
|
+
image.avg
|
44
|
+
true
|
45
|
+
rescue ::Vips::Error
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,17 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveStorage
|
4
|
-
#
|
4
|
+
# This is an abstract base class for image analyzers, which extract width and height from an image blob.
|
5
5
|
#
|
6
6
|
# If the image contains EXIF data indicating its angle is 90 or 270 degrees, its width and height are swapped for convenience.
|
7
7
|
#
|
8
8
|
# Example:
|
9
9
|
#
|
10
|
-
# ActiveStorage::Analyzer::ImageAnalyzer.new(blob).metadata
|
10
|
+
# ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick.new(blob).metadata
|
11
11
|
# # => { width: 4104, height: 2736 }
|
12
|
-
#
|
13
|
-
# This analyzer relies on the third-party {MiniMagick}[https://github.com/minimagick/minimagick] gem. MiniMagick requires
|
14
|
-
# the {ImageMagick}[http://www.imagemagick.org] system library.
|
15
12
|
class Analyzer::ImageAnalyzer < Analyzer
|
16
13
|
def self.accept?(blob)
|
17
14
|
blob.image?
|
@@ -26,30 +23,5 @@ module ActiveStorage
|
|
26
23
|
end
|
27
24
|
end
|
28
25
|
end
|
29
|
-
|
30
|
-
private
|
31
|
-
def read_image
|
32
|
-
download_blob_to_tempfile do |file|
|
33
|
-
require "mini_magick"
|
34
|
-
image = MiniMagick::Image.new(file.path)
|
35
|
-
|
36
|
-
if image.valid?
|
37
|
-
yield image
|
38
|
-
else
|
39
|
-
logger.info "Skipping image analysis because ImageMagick doesn't support the file"
|
40
|
-
{}
|
41
|
-
end
|
42
|
-
end
|
43
|
-
rescue LoadError
|
44
|
-
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
|
45
|
-
{}
|
46
|
-
rescue MiniMagick::Error => error
|
47
|
-
logger.error "Skipping image analysis due to an ImageMagick error: #{error.message}"
|
48
|
-
{}
|
49
|
-
end
|
50
|
-
|
51
|
-
def rotated_image?(image)
|
52
|
-
%w[ RightTop LeftBottom ].include?(image["%[orientation]"])
|
53
|
-
end
|
54
26
|
end
|
55
27
|
end
|
@@ -8,11 +8,13 @@ module ActiveStorage
|
|
8
8
|
# * Duration (seconds)
|
9
9
|
# * Angle (degrees)
|
10
10
|
# * Display aspect ratio
|
11
|
+
# * Audio (true if file has an audio channel, false if not)
|
12
|
+
# * Video (true if file has an video channel, false if not)
|
11
13
|
#
|
12
14
|
# Example:
|
13
15
|
#
|
14
16
|
# ActiveStorage::Analyzer::VideoAnalyzer.new(blob).metadata
|
15
|
-
# # => { width: 640.0, height: 480.0, duration: 5.0, angle: 0, display_aspect_ratio: [4, 3] }
|
17
|
+
# # => { width: 640.0, height: 480.0, duration: 5.0, angle: 0, display_aspect_ratio: [4, 3], audio: true, video: true }
|
16
18
|
#
|
17
19
|
# When a video's angle is 90 or 270 degrees, its width and height are automatically swapped for convenience.
|
18
20
|
#
|
@@ -23,7 +25,7 @@ module ActiveStorage
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def metadata
|
26
|
-
{ width: width, height: height, duration: duration, angle: angle, display_aspect_ratio: display_aspect_ratio }.compact
|
28
|
+
{ width: width, height: height, duration: duration, angle: angle, display_aspect_ratio: display_aspect_ratio, audio: audio?, video: video? }.compact
|
27
29
|
end
|
28
30
|
|
29
31
|
private
|
@@ -63,11 +65,18 @@ module ActiveStorage
|
|
63
65
|
end
|
64
66
|
end
|
65
67
|
|
66
|
-
|
67
68
|
def rotated?
|
68
69
|
angle == 90 || angle == 270
|
69
70
|
end
|
70
71
|
|
72
|
+
def audio?
|
73
|
+
audio_stream.present?
|
74
|
+
end
|
75
|
+
|
76
|
+
def video?
|
77
|
+
video_stream.present?
|
78
|
+
end
|
79
|
+
|
71
80
|
def computed_height
|
72
81
|
if encoded_width && display_height_scale
|
73
82
|
encoded_width * display_height_scale
|
@@ -95,6 +104,10 @@ module ActiveStorage
|
|
95
104
|
@video_stream ||= streams.detect { |stream| stream["codec_type"] == "video" } || {}
|
96
105
|
end
|
97
106
|
|
107
|
+
def audio_stream
|
108
|
+
@audio_stream ||= streams.detect { |stream| stream["codec_type"] == "audio" } || {}
|
109
|
+
end
|
110
|
+
|
98
111
|
def streams
|
99
112
|
probe["streams"] || []
|
100
113
|
end
|
@@ -108,14 +121,16 @@ module ActiveStorage
|
|
108
121
|
end
|
109
122
|
|
110
123
|
def probe_from(file)
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
124
|
+
instrument(File.basename(ffprobe_path)) do
|
125
|
+
IO.popen([ ffprobe_path,
|
126
|
+
"-print_format", "json",
|
127
|
+
"-show_streams",
|
128
|
+
"-show_format",
|
129
|
+
"-v", "error",
|
130
|
+
file.path
|
131
|
+
]) do |output|
|
132
|
+
JSON.parse(output.read)
|
133
|
+
end
|
119
134
|
end
|
120
135
|
rescue Errno::ENOENT
|
121
136
|
logger.info "Skipping video analysis because FFmpeg isn't installed"
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module ActiveStorage
|
4
4
|
# This is an abstract base class for analyzers, which extract metadata from blobs. See
|
5
|
-
# ActiveStorage::Analyzer::
|
5
|
+
# ActiveStorage::Analyzer::VideoAnalyzer for an example of a concrete subclass.
|
6
6
|
class Analyzer
|
7
7
|
attr_reader :blob
|
8
8
|
|
@@ -29,16 +29,20 @@ module ActiveStorage
|
|
29
29
|
|
30
30
|
private
|
31
31
|
# Downloads the blob to a tempfile on disk. Yields the tempfile.
|
32
|
-
def download_blob_to_tempfile(&block)
|
32
|
+
def download_blob_to_tempfile(&block) # :doc:
|
33
33
|
blob.open tmpdir: tmpdir, &block
|
34
34
|
end
|
35
35
|
|
36
|
-
def logger
|
36
|
+
def logger # :doc:
|
37
37
|
ActiveStorage.logger
|
38
38
|
end
|
39
39
|
|
40
|
-
def tmpdir
|
40
|
+
def tmpdir # :doc:
|
41
41
|
Dir.tmpdir
|
42
42
|
end
|
43
|
+
|
44
|
+
def instrument(analyzer, &block) # :doc:
|
45
|
+
ActiveSupport::Notifications.instrument("analyze.active_storage", analyzer: analyzer, &block)
|
46
|
+
end
|
43
47
|
end
|
44
48
|
end
|
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveStorage
|
4
|
-
class Attached::Changes::CreateMany
|
4
|
+
class Attached::Changes::CreateMany # :nodoc:
|
5
5
|
attr_reader :name, :record, :attachables
|
6
6
|
|
7
7
|
def initialize(name, record, attachables)
|
8
8
|
@name, @record, @attachables = name, record, Array(attachables)
|
9
9
|
blobs.each(&:identify_without_saving)
|
10
|
+
attachments
|
10
11
|
end
|
11
12
|
|
12
13
|
def attachments
|
@@ -35,13 +36,16 @@ module ActiveStorage
|
|
35
36
|
ActiveStorage::Attached::Changes::CreateOneOfMany.new(name, record, attachable)
|
36
37
|
end
|
37
38
|
|
38
|
-
|
39
39
|
def assign_associated_attachments
|
40
|
-
record.public_send("#{name}_attachments=",
|
40
|
+
record.public_send("#{name}_attachments=", persisted_or_new_attachments)
|
41
41
|
end
|
42
42
|
|
43
43
|
def reset_associated_blobs
|
44
44
|
record.public_send("#{name}_blobs").reset
|
45
45
|
end
|
46
|
+
|
47
|
+
def persisted_or_new_attachments
|
48
|
+
attachments.select { |attachment| attachment.persisted? || attachment.new_record? }
|
49
|
+
end
|
46
50
|
end
|
47
51
|
end
|
@@ -4,7 +4,7 @@ require "action_dispatch"
|
|
4
4
|
require "action_dispatch/http/upload"
|
5
5
|
|
6
6
|
module ActiveStorage
|
7
|
-
class Attached::Changes::CreateOne
|
7
|
+
class Attached::Changes::CreateOne # :nodoc:
|
8
8
|
attr_reader :name, :record, :attachable
|
9
9
|
|
10
10
|
def initialize(name, record, attachable)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveStorage
|
4
|
-
class Attached::Changes::CreateOneOfMany < Attached::Changes::CreateOne
|
4
|
+
class Attached::Changes::CreateOneOfMany < Attached::Changes::CreateOne # :nodoc:
|
5
5
|
private
|
6
6
|
def find_attachment
|
7
7
|
record.public_send("#{name}_attachments").detect { |attachment| attachment.blob_id == blob.id }
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
class Attached::Changes::DetachMany # :nodoc:
|
5
|
+
attr_reader :name, :record, :attachments
|
6
|
+
|
7
|
+
def initialize(name, record, attachments)
|
8
|
+
@name, @record, @attachments = name, record, attachments
|
9
|
+
end
|
10
|
+
|
11
|
+
def detach
|
12
|
+
if attachments.any?
|
13
|
+
attachments.delete_all if attachments.respond_to?(:delete_all)
|
14
|
+
record.attachment_changes.delete(name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
class Attached::Changes::DetachOne # :nodoc:
|
5
|
+
attr_reader :name, :record, :attachment
|
6
|
+
|
7
|
+
def initialize(name, record, attachment)
|
8
|
+
@name, @record, @attachment = name, record, attachment
|
9
|
+
end
|
10
|
+
|
11
|
+
def detach
|
12
|
+
if attachment.present?
|
13
|
+
attachment.delete
|
14
|
+
reset
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def reset
|
20
|
+
record.attachment_changes.delete(name)
|
21
|
+
record.public_send("#{name}_attachment=", nil)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
class Attached::Changes::PurgeMany # :nodoc:
|
5
|
+
attr_reader :name, :record, :attachments
|
6
|
+
|
7
|
+
def initialize(name, record, attachments)
|
8
|
+
@name, @record, @attachments = name, record, attachments
|
9
|
+
end
|
10
|
+
|
11
|
+
def purge
|
12
|
+
attachments.each(&:purge)
|
13
|
+
reset
|
14
|
+
end
|
15
|
+
|
16
|
+
def purge_later
|
17
|
+
attachments.each(&:purge_later)
|
18
|
+
reset
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def reset
|
23
|
+
record.attachment_changes.delete(name)
|
24
|
+
record.public_send("#{name}_attachments").reset
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
class Attached::Changes::PurgeOne # :nodoc:
|
5
|
+
attr_reader :name, :record, :attachment
|
6
|
+
|
7
|
+
def initialize(name, record, attachment)
|
8
|
+
@name, @record, @attachment = name, record, attachment
|
9
|
+
end
|
10
|
+
|
11
|
+
def purge
|
12
|
+
attachment&.purge
|
13
|
+
reset
|
14
|
+
end
|
15
|
+
|
16
|
+
def purge_later
|
17
|
+
attachment&.purge_later
|
18
|
+
reset
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def reset
|
23
|
+
record.attachment_changes.delete(name)
|
24
|
+
record.public_send("#{name}_attachment=", nil)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveStorage
|
4
|
-
module Attached::Changes
|
4
|
+
module Attached::Changes # :nodoc:
|
5
5
|
extend ActiveSupport::Autoload
|
6
6
|
|
7
7
|
eager_autoload do
|
@@ -11,6 +11,12 @@ module ActiveStorage
|
|
11
11
|
|
12
12
|
autoload :DeleteOne
|
13
13
|
autoload :DeleteMany
|
14
|
+
|
15
|
+
autoload :DetachOne
|
16
|
+
autoload :DetachMany
|
17
|
+
|
18
|
+
autoload :PurgeOne
|
19
|
+
autoload :PurgeMany
|
14
20
|
end
|
15
21
|
end
|
16
22
|
end
|