activestorage 6.0.0
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 +7 -0
- data/CHANGELOG.md +198 -0
- data/MIT-LICENSE +20 -0
- data/README.md +162 -0
- data/app/assets/javascripts/activestorage.js +942 -0
- data/app/controllers/active_storage/base_controller.rb +8 -0
- data/app/controllers/active_storage/blobs_controller.rb +14 -0
- data/app/controllers/active_storage/direct_uploads_controller.rb +23 -0
- data/app/controllers/active_storage/disk_controller.rb +66 -0
- data/app/controllers/active_storage/representations_controller.rb +14 -0
- data/app/controllers/concerns/active_storage/set_blob.rb +16 -0
- data/app/controllers/concerns/active_storage/set_current.rb +15 -0
- data/app/javascript/activestorage/blob_record.js +73 -0
- data/app/javascript/activestorage/blob_upload.js +35 -0
- data/app/javascript/activestorage/direct_upload.js +48 -0
- data/app/javascript/activestorage/direct_upload_controller.js +67 -0
- data/app/javascript/activestorage/direct_uploads_controller.js +50 -0
- data/app/javascript/activestorage/file_checksum.js +53 -0
- data/app/javascript/activestorage/helpers.js +51 -0
- data/app/javascript/activestorage/index.js +11 -0
- data/app/javascript/activestorage/ujs.js +86 -0
- data/app/jobs/active_storage/analyze_job.rb +12 -0
- data/app/jobs/active_storage/base_job.rb +4 -0
- data/app/jobs/active_storage/purge_job.rb +13 -0
- data/app/models/active_storage/attachment.rb +50 -0
- data/app/models/active_storage/blob.rb +278 -0
- data/app/models/active_storage/blob/analyzable.rb +57 -0
- data/app/models/active_storage/blob/identifiable.rb +31 -0
- data/app/models/active_storage/blob/representable.rb +93 -0
- data/app/models/active_storage/current.rb +5 -0
- data/app/models/active_storage/filename.rb +77 -0
- data/app/models/active_storage/preview.rb +89 -0
- data/app/models/active_storage/variant.rb +131 -0
- data/app/models/active_storage/variation.rb +80 -0
- data/config/routes.rb +32 -0
- data/db/migrate/20170806125915_create_active_storage_tables.rb +26 -0
- data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +9 -0
- data/lib/active_storage.rb +73 -0
- data/lib/active_storage/analyzer.rb +38 -0
- data/lib/active_storage/analyzer/image_analyzer.rb +52 -0
- data/lib/active_storage/analyzer/null_analyzer.rb +13 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +118 -0
- data/lib/active_storage/attached.rb +25 -0
- data/lib/active_storage/attached/changes.rb +16 -0
- data/lib/active_storage/attached/changes/create_many.rb +46 -0
- data/lib/active_storage/attached/changes/create_one.rb +69 -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 +65 -0
- data/lib/active_storage/attached/model.rb +147 -0
- data/lib/active_storage/attached/one.rb +79 -0
- data/lib/active_storage/downloader.rb +43 -0
- data/lib/active_storage/downloading.rb +47 -0
- data/lib/active_storage/engine.rb +149 -0
- data/lib/active_storage/errors.rb +26 -0
- data/lib/active_storage/gem_version.rb +17 -0
- data/lib/active_storage/log_subscriber.rb +58 -0
- data/lib/active_storage/previewer.rb +84 -0
- data/lib/active_storage/previewer/mupdf_previewer.rb +36 -0
- data/lib/active_storage/previewer/poppler_pdf_previewer.rb +35 -0
- data/lib/active_storage/previewer/video_previewer.rb +26 -0
- data/lib/active_storage/reflection.rb +64 -0
- data/lib/active_storage/service.rb +141 -0
- data/lib/active_storage/service/azure_storage_service.rb +165 -0
- data/lib/active_storage/service/configurator.rb +34 -0
- data/lib/active_storage/service/disk_service.rb +166 -0
- data/lib/active_storage/service/gcs_service.rb +141 -0
- data/lib/active_storage/service/mirror_service.rb +55 -0
- data/lib/active_storage/service/s3_service.rb +116 -0
- data/lib/active_storage/transformers/image_processing_transformer.rb +39 -0
- data/lib/active_storage/transformers/mini_magick_transformer.rb +38 -0
- data/lib/active_storage/transformers/transformer.rb +42 -0
- data/lib/active_storage/version.rb +10 -0
- data/lib/tasks/activestorage.rake +22 -0
- metadata +174 -0
@@ -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
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tmpdir"
|
4
|
+
require "active_support/core_ext/string/filters"
|
5
|
+
|
6
|
+
module ActiveStorage
|
7
|
+
module Downloading
|
8
|
+
def self.included(klass)
|
9
|
+
ActiveSupport::Deprecation.warn <<~MESSAGE.squish, caller_locations(2)
|
10
|
+
ActiveStorage::Downloading is deprecated and will be removed in Active Storage 6.1.
|
11
|
+
Use ActiveStorage::Blob#open instead.
|
12
|
+
MESSAGE
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
# Opens a new tempfile in #tempdir and copies blob data into it. Yields the tempfile.
|
17
|
+
def download_blob_to_tempfile #:doc:
|
18
|
+
open_tempfile_for_blob do |file|
|
19
|
+
download_blob_to file
|
20
|
+
yield file
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def open_tempfile_for_blob
|
25
|
+
tempfile = Tempfile.open([ "ActiveStorage", blob.filename.extension_with_delimiter ], tempdir)
|
26
|
+
|
27
|
+
begin
|
28
|
+
yield tempfile
|
29
|
+
ensure
|
30
|
+
tempfile.close!
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Efficiently downloads blob data into the given file.
|
35
|
+
def download_blob_to(file) #:doc:
|
36
|
+
file.binmode
|
37
|
+
blob.download { |chunk| file.write(chunk) }
|
38
|
+
file.flush
|
39
|
+
file.rewind
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the directory in which tempfiles should be opened. Defaults to +Dir.tmpdir+.
|
43
|
+
def tempdir #:doc:
|
44
|
+
Dir.tmpdir
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
require "action_controller/railtie"
|
5
|
+
require "active_job/railtie"
|
6
|
+
require "active_record/railtie"
|
7
|
+
|
8
|
+
require "active_storage"
|
9
|
+
|
10
|
+
require "active_storage/previewer/poppler_pdf_previewer"
|
11
|
+
require "active_storage/previewer/mupdf_previewer"
|
12
|
+
require "active_storage/previewer/video_previewer"
|
13
|
+
|
14
|
+
require "active_storage/analyzer/image_analyzer"
|
15
|
+
require "active_storage/analyzer/video_analyzer"
|
16
|
+
|
17
|
+
require "active_storage/reflection"
|
18
|
+
|
19
|
+
module ActiveStorage
|
20
|
+
class Engine < Rails::Engine # :nodoc:
|
21
|
+
isolate_namespace ActiveStorage
|
22
|
+
|
23
|
+
config.active_storage = ActiveSupport::OrderedOptions.new
|
24
|
+
config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
|
25
|
+
config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
|
26
|
+
config.active_storage.paths = ActiveSupport::OrderedOptions.new
|
27
|
+
config.active_storage.queues = ActiveSupport::OrderedOptions.new
|
28
|
+
|
29
|
+
config.active_storage.variable_content_types = %w(
|
30
|
+
image/png
|
31
|
+
image/gif
|
32
|
+
image/jpg
|
33
|
+
image/jpeg
|
34
|
+
image/pjpeg
|
35
|
+
image/tiff
|
36
|
+
image/bmp
|
37
|
+
image/vnd.adobe.photoshop
|
38
|
+
image/vnd.microsoft.icon
|
39
|
+
)
|
40
|
+
|
41
|
+
config.active_storage.content_types_to_serve_as_binary = %w(
|
42
|
+
text/html
|
43
|
+
text/javascript
|
44
|
+
image/svg+xml
|
45
|
+
application/postscript
|
46
|
+
application/x-shockwave-flash
|
47
|
+
text/xml
|
48
|
+
application/xml
|
49
|
+
application/xhtml+xml
|
50
|
+
application/mathml+xml
|
51
|
+
text/cache-manifest
|
52
|
+
)
|
53
|
+
|
54
|
+
config.active_storage.content_types_allowed_inline = %w(
|
55
|
+
image/png
|
56
|
+
image/gif
|
57
|
+
image/jpg
|
58
|
+
image/jpeg
|
59
|
+
image/tiff
|
60
|
+
image/bmp
|
61
|
+
image/vnd.adobe.photoshop
|
62
|
+
image/vnd.microsoft.icon
|
63
|
+
application/pdf
|
64
|
+
)
|
65
|
+
|
66
|
+
config.eager_load_namespaces << ActiveStorage
|
67
|
+
|
68
|
+
initializer "active_storage.configs" do
|
69
|
+
config.after_initialize do |app|
|
70
|
+
ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
|
71
|
+
ActiveStorage.variant_processor = app.config.active_storage.variant_processor || :mini_magick
|
72
|
+
ActiveStorage.previewers = app.config.active_storage.previewers || []
|
73
|
+
ActiveStorage.analyzers = app.config.active_storage.analyzers || []
|
74
|
+
ActiveStorage.paths = app.config.active_storage.paths || {}
|
75
|
+
ActiveStorage.routes_prefix = app.config.active_storage.routes_prefix || "/rails/active_storage"
|
76
|
+
|
77
|
+
ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
|
78
|
+
ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
|
79
|
+
ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
|
80
|
+
ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
|
81
|
+
ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
|
82
|
+
|
83
|
+
ActiveStorage.replace_on_assign_to_many = app.config.active_storage.replace_on_assign_to_many || false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
initializer "active_storage.attached" do
|
88
|
+
require "active_storage/attached"
|
89
|
+
|
90
|
+
ActiveSupport.on_load(:active_record) do
|
91
|
+
include ActiveStorage::Attached::Model
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
initializer "active_storage.verifier" do
|
96
|
+
config.after_initialize do |app|
|
97
|
+
ActiveStorage.verifier = app.message_verifier("ActiveStorage")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
initializer "active_storage.services" do
|
102
|
+
ActiveSupport.on_load(:active_storage_blob) do
|
103
|
+
if config_choice = Rails.configuration.active_storage.service
|
104
|
+
configs = Rails.configuration.active_storage.service_configurations ||= begin
|
105
|
+
config_file = Pathname.new(Rails.root.join("config/storage.yml"))
|
106
|
+
raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist?
|
107
|
+
|
108
|
+
require "yaml"
|
109
|
+
require "erb"
|
110
|
+
|
111
|
+
YAML.load(ERB.new(config_file.read).result) || {}
|
112
|
+
rescue Psych::SyntaxError => e
|
113
|
+
raise "YAML syntax error occurred while parsing #{config_file}. " \
|
114
|
+
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
|
115
|
+
"Error: #{e.message}"
|
116
|
+
end
|
117
|
+
|
118
|
+
ActiveStorage::Blob.service =
|
119
|
+
begin
|
120
|
+
ActiveStorage::Service.configure config_choice, configs
|
121
|
+
rescue => e
|
122
|
+
raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
initializer "active_storage.queues" do
|
129
|
+
config.after_initialize do |app|
|
130
|
+
if queue = app.config.active_storage.queue
|
131
|
+
ActiveSupport::Deprecation.warn \
|
132
|
+
"config.active_storage.queue is deprecated and will be removed in Rails 6.1. " \
|
133
|
+
"Set config.active_storage.queues.purge and config.active_storage.queues.analysis instead."
|
134
|
+
|
135
|
+
ActiveStorage.queues = { purge: queue, analysis: queue }
|
136
|
+
else
|
137
|
+
ActiveStorage.queues = app.config.active_storage.queues || {}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
initializer "active_storage.reflection" do
|
143
|
+
ActiveSupport.on_load(:active_record) do
|
144
|
+
include Reflection::ActiveRecordExtensions
|
145
|
+
ActiveRecord::Reflection.singleton_class.prepend(Reflection::ReflectionExtension)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
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
|
26
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
# Returns the version of the currently loaded Active Storage as a <tt>Gem::Version</tt>.
|
5
|
+
def self.gem_version
|
6
|
+
Gem::Version.new VERSION::STRING
|
7
|
+
end
|
8
|
+
|
9
|
+
module VERSION
|
10
|
+
MAJOR = 6
|
11
|
+
MINOR = 0
|
12
|
+
TINY = 0
|
13
|
+
PRE = nil
|
14
|
+
|
15
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/log_subscriber"
|
4
|
+
|
5
|
+
module ActiveStorage
|
6
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
7
|
+
def service_upload(event)
|
8
|
+
message = "Uploaded file to key: #{key_in(event)}"
|
9
|
+
message += " (checksum: #{event.payload[:checksum]})" if event.payload[:checksum]
|
10
|
+
info event, color(message, GREEN)
|
11
|
+
end
|
12
|
+
|
13
|
+
def service_download(event)
|
14
|
+
info event, color("Downloaded file from key: #{key_in(event)}", BLUE)
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :service_streaming_download, :service_download
|
18
|
+
|
19
|
+
def service_delete(event)
|
20
|
+
info event, color("Deleted file from key: #{key_in(event)}", RED)
|
21
|
+
end
|
22
|
+
|
23
|
+
def service_delete_prefixed(event)
|
24
|
+
info event, color("Deleted files by key prefix: #{event.payload[:prefix]}", RED)
|
25
|
+
end
|
26
|
+
|
27
|
+
def service_exist(event)
|
28
|
+
debug event, color("Checked if file exists at key: #{key_in(event)} (#{event.payload[:exist] ? "yes" : "no"})", BLUE)
|
29
|
+
end
|
30
|
+
|
31
|
+
def service_url(event)
|
32
|
+
debug event, color("Generated URL for file at key: #{key_in(event)} (#{event.payload[:url]})", BLUE)
|
33
|
+
end
|
34
|
+
|
35
|
+
def logger
|
36
|
+
ActiveStorage.logger
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def info(event, colored_message)
|
41
|
+
super log_prefix_for_service(event) + colored_message
|
42
|
+
end
|
43
|
+
|
44
|
+
def debug(event, colored_message)
|
45
|
+
super log_prefix_for_service(event) + colored_message
|
46
|
+
end
|
47
|
+
|
48
|
+
def log_prefix_for_service(event)
|
49
|
+
color " #{event.payload[:service]} Storage (#{event.duration.round(1)}ms) ", CYAN
|
50
|
+
end
|
51
|
+
|
52
|
+
def key_in(event)
|
53
|
+
event.payload[:key]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
ActiveStorage::LogSubscriber.attach_to :active_storage
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
# This is an abstract base class for previewers, which generate images from blobs. See
|
5
|
+
# ActiveStorage::Previewer::MuPDFPreviewer and ActiveStorage::Previewer::VideoPreviewer for
|
6
|
+
# examples of concrete subclasses.
|
7
|
+
class Previewer
|
8
|
+
attr_reader :blob
|
9
|
+
|
10
|
+
# Implement this method in a concrete subclass. Have it return true when given a blob from which
|
11
|
+
# the previewer can generate an image.
|
12
|
+
def self.accept?(blob)
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(blob)
|
17
|
+
@blob = blob
|
18
|
+
end
|
19
|
+
|
20
|
+
# Override this method in a concrete subclass. Have it yield an attachable preview image (i.e.
|
21
|
+
# anything accepted by ActiveStorage::Attached::One#attach).
|
22
|
+
def preview
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
# Downloads the blob to a tempfile on disk. Yields the tempfile.
|
28
|
+
def download_blob_to_tempfile(&block) #:doc:
|
29
|
+
blob.open tmpdir: tmpdir, &block
|
30
|
+
end
|
31
|
+
|
32
|
+
# Executes a system command, capturing its binary output in a tempfile. Yields the tempfile.
|
33
|
+
#
|
34
|
+
# Use this method to shell out to a system library (e.g. muPDF or FFmpeg) for preview image
|
35
|
+
# generation. The resulting tempfile can be used as the +:io+ value in an attachable Hash:
|
36
|
+
#
|
37
|
+
# def preview
|
38
|
+
# download_blob_to_tempfile do |input|
|
39
|
+
# draw "my-drawing-command", input.path, "--format", "png", "-" do |output|
|
40
|
+
# yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# The output tempfile is opened in the directory returned by #tmpdir.
|
46
|
+
def draw(*argv) #:doc:
|
47
|
+
open_tempfile do |file|
|
48
|
+
instrument :preview, key: blob.key do
|
49
|
+
capture(*argv, to: file)
|
50
|
+
end
|
51
|
+
|
52
|
+
yield file
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def open_tempfile
|
57
|
+
tempfile = Tempfile.open("ActiveStorage-", tmpdir)
|
58
|
+
|
59
|
+
begin
|
60
|
+
yield tempfile
|
61
|
+
ensure
|
62
|
+
tempfile.close!
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def instrument(operation, payload = {}, &block)
|
67
|
+
ActiveSupport::Notifications.instrument "#{operation}.active_storage", payload, &block
|
68
|
+
end
|
69
|
+
|
70
|
+
def capture(*argv, to:)
|
71
|
+
to.binmode
|
72
|
+
IO.popen(argv, err: File::NULL) { |out| IO.copy_stream(out, to) }
|
73
|
+
to.rewind
|
74
|
+
end
|
75
|
+
|
76
|
+
def logger #:doc:
|
77
|
+
ActiveStorage.logger
|
78
|
+
end
|
79
|
+
|
80
|
+
def tmpdir #:doc:
|
81
|
+
Dir.tmpdir
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
class Previewer::MuPDFPreviewer < Previewer
|
5
|
+
class << self
|
6
|
+
def accept?(blob)
|
7
|
+
blob.content_type == "application/pdf" && mutool_exists?
|
8
|
+
end
|
9
|
+
|
10
|
+
def mutool_path
|
11
|
+
ActiveStorage.paths[:mutool] || "mutool"
|
12
|
+
end
|
13
|
+
|
14
|
+
def mutool_exists?
|
15
|
+
return @mutool_exists unless @mutool_exists.nil?
|
16
|
+
|
17
|
+
system mutool_path, out: File::NULL, err: File::NULL
|
18
|
+
|
19
|
+
@mutool_exists = $?.exitstatus == 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def preview
|
24
|
+
download_blob_to_tempfile do |input|
|
25
|
+
draw_first_page_from input do |output|
|
26
|
+
yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def draw_first_page_from(file, &block)
|
33
|
+
draw self.class.mutool_path, "draw", "-F", "png", "-o", "-", file.path, "1", &block
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|