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.

Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +198 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +162 -0
  5. data/app/assets/javascripts/activestorage.js +942 -0
  6. data/app/controllers/active_storage/base_controller.rb +8 -0
  7. data/app/controllers/active_storage/blobs_controller.rb +14 -0
  8. data/app/controllers/active_storage/direct_uploads_controller.rb +23 -0
  9. data/app/controllers/active_storage/disk_controller.rb +66 -0
  10. data/app/controllers/active_storage/representations_controller.rb +14 -0
  11. data/app/controllers/concerns/active_storage/set_blob.rb +16 -0
  12. data/app/controllers/concerns/active_storage/set_current.rb +15 -0
  13. data/app/javascript/activestorage/blob_record.js +73 -0
  14. data/app/javascript/activestorage/blob_upload.js +35 -0
  15. data/app/javascript/activestorage/direct_upload.js +48 -0
  16. data/app/javascript/activestorage/direct_upload_controller.js +67 -0
  17. data/app/javascript/activestorage/direct_uploads_controller.js +50 -0
  18. data/app/javascript/activestorage/file_checksum.js +53 -0
  19. data/app/javascript/activestorage/helpers.js +51 -0
  20. data/app/javascript/activestorage/index.js +11 -0
  21. data/app/javascript/activestorage/ujs.js +86 -0
  22. data/app/jobs/active_storage/analyze_job.rb +12 -0
  23. data/app/jobs/active_storage/base_job.rb +4 -0
  24. data/app/jobs/active_storage/purge_job.rb +13 -0
  25. data/app/models/active_storage/attachment.rb +50 -0
  26. data/app/models/active_storage/blob.rb +278 -0
  27. data/app/models/active_storage/blob/analyzable.rb +57 -0
  28. data/app/models/active_storage/blob/identifiable.rb +31 -0
  29. data/app/models/active_storage/blob/representable.rb +93 -0
  30. data/app/models/active_storage/current.rb +5 -0
  31. data/app/models/active_storage/filename.rb +77 -0
  32. data/app/models/active_storage/preview.rb +89 -0
  33. data/app/models/active_storage/variant.rb +131 -0
  34. data/app/models/active_storage/variation.rb +80 -0
  35. data/config/routes.rb +32 -0
  36. data/db/migrate/20170806125915_create_active_storage_tables.rb +26 -0
  37. data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +9 -0
  38. data/lib/active_storage.rb +73 -0
  39. data/lib/active_storage/analyzer.rb +38 -0
  40. data/lib/active_storage/analyzer/image_analyzer.rb +52 -0
  41. data/lib/active_storage/analyzer/null_analyzer.rb +13 -0
  42. data/lib/active_storage/analyzer/video_analyzer.rb +118 -0
  43. data/lib/active_storage/attached.rb +25 -0
  44. data/lib/active_storage/attached/changes.rb +16 -0
  45. data/lib/active_storage/attached/changes/create_many.rb +46 -0
  46. data/lib/active_storage/attached/changes/create_one.rb +69 -0
  47. data/lib/active_storage/attached/changes/create_one_of_many.rb +10 -0
  48. data/lib/active_storage/attached/changes/delete_many.rb +27 -0
  49. data/lib/active_storage/attached/changes/delete_one.rb +19 -0
  50. data/lib/active_storage/attached/many.rb +65 -0
  51. data/lib/active_storage/attached/model.rb +147 -0
  52. data/lib/active_storage/attached/one.rb +79 -0
  53. data/lib/active_storage/downloader.rb +43 -0
  54. data/lib/active_storage/downloading.rb +47 -0
  55. data/lib/active_storage/engine.rb +149 -0
  56. data/lib/active_storage/errors.rb +26 -0
  57. data/lib/active_storage/gem_version.rb +17 -0
  58. data/lib/active_storage/log_subscriber.rb +58 -0
  59. data/lib/active_storage/previewer.rb +84 -0
  60. data/lib/active_storage/previewer/mupdf_previewer.rb +36 -0
  61. data/lib/active_storage/previewer/poppler_pdf_previewer.rb +35 -0
  62. data/lib/active_storage/previewer/video_previewer.rb +26 -0
  63. data/lib/active_storage/reflection.rb +64 -0
  64. data/lib/active_storage/service.rb +141 -0
  65. data/lib/active_storage/service/azure_storage_service.rb +165 -0
  66. data/lib/active_storage/service/configurator.rb +34 -0
  67. data/lib/active_storage/service/disk_service.rb +166 -0
  68. data/lib/active_storage/service/gcs_service.rb +141 -0
  69. data/lib/active_storage/service/mirror_service.rb +55 -0
  70. data/lib/active_storage/service/s3_service.rb +116 -0
  71. data/lib/active_storage/transformers/image_processing_transformer.rb +39 -0
  72. data/lib/active_storage/transformers/mini_magick_transformer.rb +38 -0
  73. data/lib/active_storage/transformers/transformer.rb +42 -0
  74. data/lib/active_storage/version.rb +10 -0
  75. data/lib/tasks/activestorage.rake +22 -0
  76. 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