activestorage 5.2.4.4 → 6.1.1
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 +180 -69
- data/MIT-LICENSE +1 -1
- data/README.md +43 -8
- data/app/assets/javascripts/activestorage.js +5 -2
- data/app/controllers/active_storage/base_controller.rb +13 -4
- data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -0
- data/app/controllers/active_storage/{blobs_controller.rb → blobs/redirect_controller.rb} +3 -3
- data/app/controllers/active_storage/direct_uploads_controller.rb +2 -2
- data/app/controllers/active_storage/disk_controller.rb +13 -22
- data/app/controllers/active_storage/representations/proxy_controller.rb +19 -0
- data/app/controllers/active_storage/{representations_controller.rb → representations/redirect_controller.rb} +3 -3
- data/app/controllers/concerns/active_storage/file_server.rb +18 -0
- data/app/controllers/concerns/active_storage/set_blob.rb +1 -1
- data/app/controllers/concerns/active_storage/set_current.rb +15 -0
- data/app/controllers/concerns/active_storage/set_headers.rb +12 -0
- data/app/javascript/activestorage/blob_record.js +7 -2
- data/app/jobs/active_storage/analyze_job.rb +5 -0
- data/app/jobs/active_storage/base_job.rb +0 -1
- data/app/jobs/active_storage/mirror_job.rb +15 -0
- data/app/jobs/active_storage/purge_job.rb +3 -0
- data/app/models/active_storage/attachment.rb +35 -16
- data/app/models/active_storage/blob.rb +178 -68
- data/app/models/active_storage/blob/analyzable.rb +6 -2
- data/app/models/active_storage/blob/identifiable.rb +7 -6
- data/app/models/active_storage/blob/representable.rb +36 -6
- data/app/models/active_storage/filename.rb +0 -6
- data/app/models/active_storage/preview.rb +37 -12
- data/app/models/active_storage/record.rb +7 -0
- data/app/models/active_storage/variant.rb +53 -67
- data/app/models/active_storage/variant_record.rb +8 -0
- data/app/models/active_storage/variant_with_record.rb +54 -0
- data/app/models/active_storage/variation.rb +30 -34
- data/config/routes.rb +66 -15
- data/db/migrate/20170806125915_create_active_storage_tables.rb +14 -5
- data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +17 -0
- data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +11 -0
- data/lib/active_storage.rb +29 -6
- data/lib/active_storage/analyzer.rb +15 -4
- data/lib/active_storage/analyzer/image_analyzer.rb +14 -4
- data/lib/active_storage/analyzer/null_analyzer.rb +4 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +17 -8
- data/lib/active_storage/attached.rb +7 -22
- data/lib/active_storage/attached/changes.rb +16 -0
- data/lib/active_storage/attached/changes/create_many.rb +47 -0
- data/lib/active_storage/attached/changes/create_one.rb +82 -0
- data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
- data/lib/active_storage/attached/changes/delete_many.rb +27 -0
- data/lib/active_storage/attached/changes/delete_one.rb +19 -0
- data/lib/active_storage/attached/many.rb +19 -12
- data/lib/active_storage/attached/model.rb +212 -0
- data/lib/active_storage/attached/one.rb +19 -21
- data/lib/active_storage/downloader.rb +43 -0
- data/lib/active_storage/engine.rb +58 -23
- data/lib/active_storage/errors.rb +22 -3
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/log_subscriber.rb +6 -0
- data/lib/active_storage/previewer.rb +24 -13
- data/lib/active_storage/previewer/mupdf_previewer.rb +3 -3
- data/lib/active_storage/previewer/poppler_pdf_previewer.rb +5 -5
- data/lib/active_storage/previewer/video_previewer.rb +17 -10
- data/lib/active_storage/reflection.rb +64 -0
- data/lib/active_storage/service.rb +44 -12
- data/lib/active_storage/service/azure_storage_service.rb +65 -44
- data/lib/active_storage/service/configurator.rb +6 -2
- data/lib/active_storage/service/disk_service.rb +57 -44
- data/lib/active_storage/service/gcs_service.rb +68 -64
- data/lib/active_storage/service/mirror_service.rb +31 -7
- data/lib/active_storage/service/registry.rb +32 -0
- data/lib/active_storage/service/s3_service.rb +58 -24
- data/lib/active_storage/transformers/image_processing_transformer.rb +45 -0
- data/lib/active_storage/transformers/transformer.rb +39 -0
- data/lib/tasks/activestorage.rake +7 -0
- metadata +84 -19
- data/app/models/active_storage/filename/parameters.rb +0 -36
- data/lib/active_storage/attached/macros.rb +0 -110
- data/lib/active_storage/downloading.rb +0 -39
@@ -3,39 +3,42 @@
|
|
3
3
|
module ActiveStorage
|
4
4
|
# Representation of a single attachment to a model.
|
5
5
|
class Attached::One < Attached
|
6
|
-
delegate_missing_to :attachment
|
6
|
+
delegate_missing_to :attachment, allow_nil: true
|
7
7
|
|
8
8
|
# Returns the associated attachment record.
|
9
9
|
#
|
10
10
|
# You don't have to call this method to access the attachment's methods as
|
11
11
|
# they are all available at the model level.
|
12
12
|
def attachment
|
13
|
-
record.public_send("#{name}_attachment")
|
13
|
+
change.present? ? change.attachment : record.public_send("#{name}_attachment")
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
def blank?
|
17
|
+
!attached?
|
18
|
+
end
|
19
|
+
|
20
|
+
# Attaches an +attachable+ to the record.
|
21
|
+
#
|
22
|
+
# If the record is persisted and unchanged, the attachment is saved to
|
23
|
+
# the database immediately. Otherwise, it'll be saved to the DB when the
|
24
|
+
# record is next saved.
|
17
25
|
#
|
18
26
|
# person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
|
19
27
|
# person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
|
20
28
|
# person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
|
21
29
|
# person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
|
22
30
|
def attach(attachable)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
detach
|
29
|
-
write_attachment build_attachment(blob: blob)
|
30
|
-
end
|
31
|
-
|
32
|
-
blob_was.purge_later if blob_was && dependent == :purge_later
|
31
|
+
if record.persisted? && !record.changed?
|
32
|
+
record.public_send("#{name}=", attachable)
|
33
|
+
record.save
|
34
|
+
else
|
35
|
+
record.public_send("#{name}=", attachable)
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
36
39
|
# Returns +true+ if an attachment has been made.
|
37
40
|
#
|
38
|
-
# class User <
|
41
|
+
# class User < ApplicationRecord
|
39
42
|
# has_one_attached :avatar
|
40
43
|
# end
|
41
44
|
#
|
@@ -47,7 +50,7 @@ module ActiveStorage
|
|
47
50
|
# Deletes the attachment without purging it, leaving its blob in place.
|
48
51
|
def detach
|
49
52
|
if attached?
|
50
|
-
attachment.
|
53
|
+
attachment.delete
|
51
54
|
write_attachment nil
|
52
55
|
end
|
53
56
|
end
|
@@ -65,16 +68,11 @@ module ActiveStorage
|
|
65
68
|
def purge_later
|
66
69
|
if attached?
|
67
70
|
attachment.purge_later
|
71
|
+
write_attachment nil
|
68
72
|
end
|
69
73
|
end
|
70
74
|
|
71
75
|
private
|
72
|
-
delegate :transaction, to: :record
|
73
|
-
|
74
|
-
def build_attachment(blob:)
|
75
|
-
ActiveStorage::Attachment.new(record: record, name: name, blob: blob)
|
76
|
-
end
|
77
|
-
|
78
76
|
def write_attachment(attachment)
|
79
77
|
record.public_send("#{name}_attachment=", attachment)
|
80
78
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
class Downloader #:nodoc:
|
5
|
+
attr_reader :service
|
6
|
+
|
7
|
+
def initialize(service)
|
8
|
+
@service = service
|
9
|
+
end
|
10
|
+
|
11
|
+
def open(key, checksum:, name: "ActiveStorage-", tmpdir: nil)
|
12
|
+
open_tempfile(name, tmpdir) do |file|
|
13
|
+
download key, file
|
14
|
+
verify_integrity_of file, checksum: checksum
|
15
|
+
yield file
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def open_tempfile(name, tmpdir = nil)
|
21
|
+
file = Tempfile.open(name, tmpdir)
|
22
|
+
|
23
|
+
begin
|
24
|
+
yield file
|
25
|
+
ensure
|
26
|
+
file.close!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def download(key, file)
|
31
|
+
file.binmode
|
32
|
+
service.download(key) { |chunk| file.write(chunk) }
|
33
|
+
file.flush
|
34
|
+
file.rewind
|
35
|
+
end
|
36
|
+
|
37
|
+
def verify_integrity_of(file, checksum:)
|
38
|
+
unless Digest::MD5.file(file).base64digest == checksum
|
39
|
+
raise ActiveStorage::IntegrityError
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,6 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails"
|
4
|
+
require "action_controller/railtie"
|
5
|
+
require "active_job/railtie"
|
6
|
+
require "active_record/railtie"
|
7
|
+
|
4
8
|
require "active_storage"
|
5
9
|
|
6
10
|
require "active_storage/previewer/poppler_pdf_previewer"
|
@@ -10,6 +14,10 @@ require "active_storage/previewer/video_previewer"
|
|
10
14
|
require "active_storage/analyzer/image_analyzer"
|
11
15
|
require "active_storage/analyzer/video_analyzer"
|
12
16
|
|
17
|
+
require "active_storage/service/registry"
|
18
|
+
|
19
|
+
require "active_storage/reflection"
|
20
|
+
|
13
21
|
module ActiveStorage
|
14
22
|
class Engine < Rails::Engine # :nodoc:
|
15
23
|
isolate_namespace ActiveStorage
|
@@ -18,14 +26,26 @@ module ActiveStorage
|
|
18
26
|
config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
|
19
27
|
config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
|
20
28
|
config.active_storage.paths = ActiveSupport::OrderedOptions.new
|
29
|
+
config.active_storage.queues = ActiveSupport::InheritableOptions.new
|
21
30
|
|
22
31
|
config.active_storage.variable_content_types = %w(
|
23
32
|
image/png
|
24
33
|
image/gif
|
25
34
|
image/jpg
|
26
35
|
image/jpeg
|
36
|
+
image/pjpeg
|
37
|
+
image/tiff
|
38
|
+
image/bmp
|
27
39
|
image/vnd.adobe.photoshop
|
28
40
|
image/vnd.microsoft.icon
|
41
|
+
image/webp
|
42
|
+
)
|
43
|
+
|
44
|
+
config.active_storage.web_image_content_types = %w(
|
45
|
+
image/png
|
46
|
+
image/jpeg
|
47
|
+
image/jpg
|
48
|
+
image/gif
|
29
49
|
)
|
30
50
|
|
31
51
|
config.active_storage.content_types_to_serve_as_binary = %w(
|
@@ -46,6 +66,8 @@ module ActiveStorage
|
|
46
66
|
image/gif
|
47
67
|
image/jpg
|
48
68
|
image/jpeg
|
69
|
+
image/tiff
|
70
|
+
image/bmp
|
49
71
|
image/vnd.adobe.photoshop
|
50
72
|
image/vnd.microsoft.icon
|
51
73
|
application/pdf
|
@@ -55,16 +77,24 @@ module ActiveStorage
|
|
55
77
|
|
56
78
|
initializer "active_storage.configs" do
|
57
79
|
config.after_initialize do |app|
|
58
|
-
ActiveStorage.logger
|
59
|
-
ActiveStorage.
|
60
|
-
ActiveStorage.previewers
|
61
|
-
ActiveStorage.analyzers
|
62
|
-
ActiveStorage.paths
|
80
|
+
ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
|
81
|
+
ActiveStorage.variant_processor = app.config.active_storage.variant_processor || :mini_magick
|
82
|
+
ActiveStorage.previewers = app.config.active_storage.previewers || []
|
83
|
+
ActiveStorage.analyzers = app.config.active_storage.analyzers || []
|
84
|
+
ActiveStorage.paths = app.config.active_storage.paths || {}
|
85
|
+
ActiveStorage.routes_prefix = app.config.active_storage.routes_prefix || "/rails/active_storage"
|
86
|
+
ActiveStorage.draw_routes = app.config.active_storage.draw_routes != false
|
87
|
+
ActiveStorage.resolve_model_to_route = app.config.active_storage.resolve_model_to_route || :rails_storage_redirect
|
63
88
|
|
64
89
|
ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
|
90
|
+
ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || []
|
65
91
|
ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
|
92
|
+
ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
|
66
93
|
ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
|
67
94
|
ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
|
95
|
+
|
96
|
+
ActiveStorage.replace_on_assign_to_many = app.config.active_storage.replace_on_assign_to_many || false
|
97
|
+
ActiveStorage.track_variants = app.config.active_storage.track_variants || false
|
68
98
|
end
|
69
99
|
end
|
70
100
|
|
@@ -72,7 +102,7 @@ module ActiveStorage
|
|
72
102
|
require "active_storage/attached"
|
73
103
|
|
74
104
|
ActiveSupport.on_load(:active_record) do
|
75
|
-
|
105
|
+
include ActiveStorage::Attached::Model
|
76
106
|
end
|
77
107
|
end
|
78
108
|
|
@@ -84,29 +114,34 @@ module ActiveStorage
|
|
84
114
|
|
85
115
|
initializer "active_storage.services" do
|
86
116
|
ActiveSupport.on_load(:active_storage_blob) do
|
87
|
-
|
88
|
-
|
89
|
-
config_file =
|
117
|
+
configs = Rails.configuration.active_storage.service_configurations ||=
|
118
|
+
begin
|
119
|
+
config_file = Rails.root.join("config/storage/#{Rails.env}.yml")
|
120
|
+
config_file = Rails.root.join("config/storage.yml") unless config_file.exist?
|
90
121
|
raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist?
|
91
122
|
|
92
|
-
|
93
|
-
require "erb"
|
94
|
-
|
95
|
-
YAML.load(ERB.new(config_file.read).result) || {}
|
96
|
-
rescue Psych::SyntaxError => e
|
97
|
-
raise "YAML syntax error occurred while parsing #{config_file}. " \
|
98
|
-
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
|
99
|
-
"Error: #{e.message}"
|
123
|
+
ActiveSupport::ConfigurationFile.parse(config_file)
|
100
124
|
end
|
101
125
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace
|
107
|
-
end
|
126
|
+
ActiveStorage::Blob.services = ActiveStorage::Service::Registry.new(configs)
|
127
|
+
|
128
|
+
if config_choice = Rails.configuration.active_storage.service
|
129
|
+
ActiveStorage::Blob.service = ActiveStorage::Blob.services.fetch(config_choice)
|
108
130
|
end
|
109
131
|
end
|
110
132
|
end
|
133
|
+
|
134
|
+
initializer "active_storage.queues" do
|
135
|
+
config.after_initialize do |app|
|
136
|
+
ActiveStorage.queues = app.config.active_storage.queues || {}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
initializer "active_storage.reflection" do
|
141
|
+
ActiveSupport.on_load(:active_record) do
|
142
|
+
include Reflection::ActiveRecordExtensions
|
143
|
+
ActiveRecord::Reflection.singleton_class.prepend(Reflection::ReflectionExtension)
|
144
|
+
end
|
145
|
+
end
|
111
146
|
end
|
112
147
|
end
|
@@ -1,7 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveStorage
|
4
|
-
class
|
5
|
-
class
|
6
|
-
|
4
|
+
# Generic base class for all Active Storage exceptions.
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
# Raised when ActiveStorage::Blob#variant is called on a blob that isn't variable.
|
8
|
+
# Use ActiveStorage::Blob#variable? to determine whether a blob is variable.
|
9
|
+
class InvariableError < Error; end
|
10
|
+
|
11
|
+
# Raised when ActiveStorage::Blob#preview is called on a blob that isn't previewable.
|
12
|
+
# Use ActiveStorage::Blob#previewable? to determine whether a blob is previewable.
|
13
|
+
class UnpreviewableError < Error; end
|
14
|
+
|
15
|
+
# Raised when ActiveStorage::Blob#representation is called on a blob that isn't representable.
|
16
|
+
# Use ActiveStorage::Blob#representable? to determine whether a blob is representable.
|
17
|
+
class UnrepresentableError < Error; end
|
18
|
+
|
19
|
+
# Raised when uploaded or downloaded data does not match a precomputed checksum.
|
20
|
+
# Indicates that a network error or a software bug caused data corruption.
|
21
|
+
class IntegrityError < Error; end
|
22
|
+
|
23
|
+
# Raised when ActiveStorage::Blob#download is called on a blob where the
|
24
|
+
# backing file is no longer present in its service.
|
25
|
+
class FileNotFoundError < Error; end
|
7
26
|
end
|
@@ -32,6 +32,12 @@ module ActiveStorage
|
|
32
32
|
debug event, color("Generated URL for file at key: #{key_in(event)} (#{event.payload[:url]})", BLUE)
|
33
33
|
end
|
34
34
|
|
35
|
+
def service_mirror(event)
|
36
|
+
message = "Mirrored file at key: #{key_in(event)}"
|
37
|
+
message += " (checksum: #{event.payload[:checksum]})" if event.payload[:checksum]
|
38
|
+
debug event, color(message, GREEN)
|
39
|
+
end
|
40
|
+
|
35
41
|
def logger
|
36
42
|
ActiveStorage.logger
|
37
43
|
end
|
@@ -1,14 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_storage/downloading"
|
4
|
-
|
5
3
|
module ActiveStorage
|
6
4
|
# This is an abstract base class for previewers, which generate images from blobs. See
|
7
5
|
# ActiveStorage::Previewer::MuPDFPreviewer and ActiveStorage::Previewer::VideoPreviewer for
|
8
6
|
# examples of concrete subclasses.
|
9
7
|
class Previewer
|
10
|
-
include Downloading
|
11
|
-
|
12
8
|
attr_reader :blob
|
13
9
|
|
14
10
|
# Implement this method in a concrete subclass. Have it return true when given a blob from which
|
@@ -22,15 +18,21 @@ module ActiveStorage
|
|
22
18
|
end
|
23
19
|
|
24
20
|
# Override this method in a concrete subclass. Have it yield an attachable preview image (i.e.
|
25
|
-
# anything accepted by ActiveStorage::Attached::One#attach).
|
26
|
-
|
21
|
+
# anything accepted by ActiveStorage::Attached::One#attach). Pass the additional options to
|
22
|
+
# the underlying blob that is created.
|
23
|
+
def preview(**options)
|
27
24
|
raise NotImplementedError
|
28
25
|
end
|
29
26
|
|
30
27
|
private
|
28
|
+
# Downloads the blob to a tempfile on disk. Yields the tempfile.
|
29
|
+
def download_blob_to_tempfile(&block) #:doc:
|
30
|
+
blob.open tmpdir: tmpdir, &block
|
31
|
+
end
|
32
|
+
|
31
33
|
# Executes a system command, capturing its binary output in a tempfile. Yields the tempfile.
|
32
34
|
#
|
33
|
-
# Use this method to shell out to a system library (e.g.
|
35
|
+
# Use this method to shell out to a system library (e.g. muPDF or FFmpeg) for preview image
|
34
36
|
# generation. The resulting tempfile can be used as the +:io+ value in an attachable Hash:
|
35
37
|
#
|
36
38
|
# def preview
|
@@ -41,18 +43,19 @@ module ActiveStorage
|
|
41
43
|
# end
|
42
44
|
# end
|
43
45
|
#
|
44
|
-
# The output tempfile is opened in the directory returned by
|
46
|
+
# The output tempfile is opened in the directory returned by #tmpdir.
|
45
47
|
def draw(*argv) #:doc:
|
46
|
-
|
47
|
-
|
48
|
+
open_tempfile do |file|
|
49
|
+
instrument :preview, key: blob.key do
|
48
50
|
capture(*argv, to: file)
|
49
|
-
yield file
|
50
51
|
end
|
52
|
+
|
53
|
+
yield file
|
51
54
|
end
|
52
55
|
end
|
53
56
|
|
54
|
-
def
|
55
|
-
tempfile = Tempfile.open("ActiveStorage",
|
57
|
+
def open_tempfile
|
58
|
+
tempfile = Tempfile.open("ActiveStorage-", tmpdir)
|
56
59
|
|
57
60
|
begin
|
58
61
|
yield tempfile
|
@@ -61,6 +64,10 @@ module ActiveStorage
|
|
61
64
|
end
|
62
65
|
end
|
63
66
|
|
67
|
+
def instrument(operation, payload = {}, &block)
|
68
|
+
ActiveSupport::Notifications.instrument "#{operation}.active_storage", payload, &block
|
69
|
+
end
|
70
|
+
|
64
71
|
def capture(*argv, to:)
|
65
72
|
to.binmode
|
66
73
|
IO.popen(argv, err: File::NULL) { |out| IO.copy_stream(out, to) }
|
@@ -70,5 +77,9 @@ module ActiveStorage
|
|
70
77
|
def logger #:doc:
|
71
78
|
ActiveStorage.logger
|
72
79
|
end
|
80
|
+
|
81
|
+
def tmpdir #:doc:
|
82
|
+
Dir.tmpdir
|
83
|
+
end
|
73
84
|
end
|
74
85
|
end
|
@@ -12,7 +12,7 @@ module ActiveStorage
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def mutool_exists?
|
15
|
-
return @mutool_exists
|
15
|
+
return @mutool_exists if defined?(@mutool_exists) && !@mutool_exists.nil?
|
16
16
|
|
17
17
|
system mutool_path, out: File::NULL, err: File::NULL
|
18
18
|
|
@@ -20,10 +20,10 @@ module ActiveStorage
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def preview
|
23
|
+
def preview(**options)
|
24
24
|
download_blob_to_tempfile do |input|
|
25
25
|
draw_first_page_from input do |output|
|
26
|
-
yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
|
26
|
+
yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png", **options
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
@@ -12,24 +12,24 @@ module ActiveStorage
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def pdftoppm_exists?
|
15
|
-
return @pdftoppm_exists
|
15
|
+
return @pdftoppm_exists if defined?(@pdftoppm_exists)
|
16
16
|
|
17
17
|
@pdftoppm_exists = system(pdftoppm_path, "-v", out: File::NULL, err: File::NULL)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
def preview
|
21
|
+
def preview(**options)
|
22
22
|
download_blob_to_tempfile do |input|
|
23
23
|
draw_first_page_from input do |output|
|
24
|
-
yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
|
24
|
+
yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png", **options
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
29
|
private
|
30
30
|
def draw_first_page_from(file, &block)
|
31
|
-
# use 72 dpi to match thumbnail
|
32
|
-
draw self.class.pdftoppm_path, "-singlefile", "-r", "72", "-png", file.path, &block
|
31
|
+
# use 72 dpi to match thumbnail dimensions of the PDF
|
32
|
+
draw self.class.pdftoppm_path, "-singlefile", "-cropbox", "-r", "72", "-png", file.path, &block
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|