shrine 2.15.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of shrine might be problematic. Click here for more details.

Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/doc/advantages.md +4 -0
  4. data/doc/plugins/activerecord.md +3 -2
  5. data/doc/plugins/add_metadata.md +4 -2
  6. data/doc/plugins/backgrounding.md +6 -4
  7. data/doc/plugins/backup.md +4 -2
  8. data/doc/plugins/cached_attachment_data.md +5 -3
  9. data/doc/plugins/copy.md +3 -1
  10. data/doc/plugins/data_uri.md +3 -2
  11. data/doc/plugins/default_storage.md +5 -2
  12. data/doc/plugins/default_url.md +4 -2
  13. data/doc/plugins/default_url_options.md +5 -2
  14. data/doc/plugins/delete_promoted.md +6 -4
  15. data/doc/plugins/delete_raw.md +12 -3
  16. data/doc/plugins/derivation_endpoint.md +17 -6
  17. data/doc/plugins/determine_mime_type.md +3 -2
  18. data/doc/plugins/direct_upload.md +4 -2
  19. data/doc/plugins/download_endpoint.md +46 -6
  20. data/doc/plugins/dynamic_storage.md +5 -2
  21. data/doc/plugins/hooks.md +3 -1
  22. data/doc/plugins/included.md +5 -2
  23. data/doc/plugins/infer_extension.md +5 -4
  24. data/doc/plugins/keep_files.md +5 -3
  25. data/doc/plugins/logging.md +4 -1
  26. data/doc/plugins/metadata_attribues.md +6 -3
  27. data/doc/plugins/migration_helpers.md +4 -2
  28. data/doc/plugins/module_include.md +4 -2
  29. data/doc/plugins/moving.md +6 -4
  30. data/doc/plugins/multi_delete.md +4 -2
  31. data/doc/plugins/parallelize.md +4 -2
  32. data/doc/plugins/parsed_json.md +5 -3
  33. data/doc/plugins/presign_endpoint.md +7 -5
  34. data/doc/plugins/pretty_location.md +4 -2
  35. data/doc/plugins/processing.md +3 -2
  36. data/doc/plugins/rack_file.md +4 -2
  37. data/doc/plugins/rack_response.md +26 -10
  38. data/doc/plugins/recache.md +7 -5
  39. data/doc/plugins/refresh_metadata.md +4 -2
  40. data/doc/plugins/remote_url.md +3 -1
  41. data/doc/plugins/remove_attachment.md +4 -2
  42. data/doc/plugins/remove_invalid.md +6 -3
  43. data/doc/plugins/restore_cached_data.md +8 -6
  44. data/doc/plugins/sequel.md +4 -1
  45. data/doc/plugins/signature.md +5 -3
  46. data/doc/plugins/store_dimensions.md +4 -2
  47. data/doc/plugins/tempfile.md +4 -2
  48. data/doc/plugins/upload_endpoint.md +4 -3
  49. data/doc/plugins/upload_options.md +4 -2
  50. data/doc/plugins/validation_helpers.md +4 -2
  51. data/doc/plugins/versions.md +3 -2
  52. data/doc/release_notes/2.15.0.md +2 -2
  53. data/doc/release_notes/2.16.0.md +52 -0
  54. data/doc/storage/s3.md +2 -2
  55. data/lib/shrine/plugins/_urlsafe_serialization.rb +2 -0
  56. data/lib/shrine/plugins/activerecord.rb +3 -0
  57. data/lib/shrine/plugins/add_metadata.rb +3 -0
  58. data/lib/shrine/plugins/backgrounding.rb +3 -0
  59. data/lib/shrine/plugins/backup.rb +3 -0
  60. data/lib/shrine/plugins/cached_attachment_data.rb +3 -0
  61. data/lib/shrine/plugins/copy.rb +3 -0
  62. data/lib/shrine/plugins/data_uri.rb +3 -0
  63. data/lib/shrine/plugins/default_storage.rb +3 -0
  64. data/lib/shrine/plugins/default_url.rb +3 -0
  65. data/lib/shrine/plugins/default_url_options.rb +3 -0
  66. data/lib/shrine/plugins/delete_promoted.rb +3 -0
  67. data/lib/shrine/plugins/delete_raw.rb +4 -1
  68. data/lib/shrine/plugins/derivation_endpoint.rb +144 -47
  69. data/lib/shrine/plugins/determine_mime_type.rb +3 -0
  70. data/lib/shrine/plugins/direct_upload.rb +3 -0
  71. data/lib/shrine/plugins/download_endpoint.rb +29 -29
  72. data/lib/shrine/plugins/dynamic_storage.rb +3 -0
  73. data/lib/shrine/plugins/hooks.rb +3 -0
  74. data/lib/shrine/plugins/included.rb +3 -0
  75. data/lib/shrine/plugins/infer_extension.rb +3 -0
  76. data/lib/shrine/plugins/keep_files.rb +3 -0
  77. data/lib/shrine/plugins/logging.rb +3 -0
  78. data/lib/shrine/plugins/metadata_attributes.rb +3 -0
  79. data/lib/shrine/plugins/migration_helpers.rb +3 -0
  80. data/lib/shrine/plugins/module_include.rb +3 -0
  81. data/lib/shrine/plugins/moving.rb +3 -0
  82. data/lib/shrine/plugins/multi_delete.rb +3 -0
  83. data/lib/shrine/plugins/parallelize.rb +5 -1
  84. data/lib/shrine/plugins/parsed_json.rb +3 -0
  85. data/lib/shrine/plugins/presign_endpoint.rb +3 -0
  86. data/lib/shrine/plugins/pretty_location.rb +3 -0
  87. data/lib/shrine/plugins/processing.rb +3 -0
  88. data/lib/shrine/plugins/rack_file.rb +3 -0
  89. data/lib/shrine/plugins/rack_response.rb +14 -14
  90. data/lib/shrine/plugins/recache.rb +3 -0
  91. data/lib/shrine/plugins/refresh_metadata.rb +3 -0
  92. data/lib/shrine/plugins/remote_url.rb +3 -0
  93. data/lib/shrine/plugins/remove_attachment.rb +3 -0
  94. data/lib/shrine/plugins/remove_invalid.rb +3 -0
  95. data/lib/shrine/plugins/restore_cached_data.rb +3 -0
  96. data/lib/shrine/plugins/sequel.rb +3 -0
  97. data/lib/shrine/plugins/signature.rb +3 -0
  98. data/lib/shrine/plugins/store_dimensions.rb +4 -1
  99. data/lib/shrine/plugins/tempfile.rb +5 -0
  100. data/lib/shrine/plugins/upload_endpoint.rb +3 -0
  101. data/lib/shrine/plugins/upload_options.rb +3 -0
  102. data/lib/shrine/plugins/validation_helpers.rb +3 -0
  103. data/lib/shrine/plugins/versions.rb +3 -0
  104. data/lib/shrine/uploaded_file.rb +15 -7
  105. data/lib/shrine/version.rb +1 -1
  106. metadata +3 -2
@@ -1,14 +1,16 @@
1
1
  # Restore Cached Data
2
2
 
3
- The `restore_cached_data` plugin re-extracts metadata when assigning already
4
- cached files, i.e. when the attachment has been retained on validation errors
5
- or assigned from a direct upload. In both cases you may want to re-extract
6
- metadata on the server side, mainly to prevent tempering, but also in case of
7
- direct uploads to obtain metadata that couldn't be extracted on the client
8
- side.
3
+ The [`restore_cached_data`][restore_cached_data] plugin re-extracts metadata
4
+ when assigning already cached files, i.e. when the attachment has been retained
5
+ on validation errors or assigned from a direct upload. In both cases you may
6
+ want to re-extract metadata on the server side, mainly to prevent tempering,
7
+ but also in case of direct uploads to obtain metadata that couldn't be
8
+ extracted on the client side.
9
9
 
10
10
  ```rb
11
11
  plugin :restore_cached_data
12
12
  ```
13
13
 
14
14
  It uses the `refresh_metadata` plugin to re-extract metadata.
15
+
16
+ [restore_cached_data]: /lib/shrine/plugins/restore_cached_data.rb
@@ -1,6 +1,7 @@
1
1
  # Sequel
2
2
 
3
- The `sequel` plugin extends the "attachment" interface with support for Sequel.
3
+ The [`sequel`][sequel] plugin extends the "attachment" interface with support
4
+ for Sequel.
4
5
 
5
6
  ```rb
6
7
  plugin :sequel
@@ -62,3 +63,5 @@ errors, you can disable it:
62
63
  ```rb
63
64
  plugin :sequel, validations: false
64
65
  ```
66
+
67
+ [sequel]: /lib/shrine/plugins/sequel.rb
@@ -1,8 +1,8 @@
1
1
  # Signature
2
2
 
3
- The `signature` plugin provides the ability to calculate a hash from file
4
- content. This hash can be used as a checksum or just as a unique signature for
5
- the uploaded file.
3
+ The [`signature`][signature] plugin provides the ability to calculate a hash
4
+ from file content. This hash can be used as a checksum or just as a unique
5
+ signature for the uploaded file.
6
6
 
7
7
  ```rb
8
8
  Shrine.plugin :signature
@@ -47,3 +47,5 @@ Shrine.calculate_signature(io, :sha256, format: :base64)
47
47
  ```
48
48
 
49
49
  The supported encoding formats are `hex` (default), `base64`, and `none`.
50
+
51
+ [signature]: /lib/shrine/plugins/signature.rb
@@ -1,7 +1,8 @@
1
1
  # Store Dimensions
2
2
 
3
- The `store_dimensions` plugin extracts dimensions of uploaded images and stores
4
- them into the metadata hash (by default it uses the [fastimage] gem).
3
+ The [`store_dimensions`][store_dimensions] plugin extracts dimensions of
4
+ uploaded images and stores them into the metadata hash (by default it uses the
5
+ [fastimage] gem).
5
6
 
6
7
  ```rb
7
8
  plugin :store_dimensions
@@ -63,6 +64,7 @@ Shrine.dimensions_analyzers[:fastimage].call(io) # calls a built-in analyzer
63
64
  #=> [300, 400]
64
65
  ```
65
66
 
67
+ [store_dimensions]: /lib/shrine/plugins/store_dimensions.rb
66
68
  [fastimage]: https://github.com/sdsykes/fastimage
67
69
  [mini_magick]: https://github.com/minimagick/minimagick
68
70
  [ruby-vips]: https://github.com/libvips/ruby-vips
@@ -1,7 +1,7 @@
1
1
  # Tempfile
2
2
 
3
- The `tempfile` plugin makes it easier to reuse a single copy of an uploaded
4
- file on disk.
3
+ The [`tempfile`][tempfile] plugin makes it easier to reuse a single copy of an
4
+ uploaded file on disk.
5
5
 
6
6
  ```rb
7
7
  Shrine.plugin :tempfile
@@ -38,3 +38,5 @@ This plugin also modifies `Shrine.with_file` to call `UploadedFile#tempfile`
38
38
  when the given IO object is an open `UploadedFile`. Since `Shrine.with_file` is
39
39
  typically called on the `Shrine` class directly, it's recommended to load this
40
40
  plugin globally.
41
+
42
+ [tempfile]: /lib/shrine/plugins/tempfile.rb
@@ -1,8 +1,8 @@
1
1
  # Upload Endpoint
2
2
 
3
- The `upload_endpoint` plugin provides a Rack endpoint which accepts file
4
- uploads and forwards them to specified storage. On the client side it's
5
- recommended to use [Uppy] for asynchronous uploads.
3
+ The [`upload_endpoint`][upload_endpoint] plugin provides a Rack endpoint which
4
+ accepts file uploads and forwards them to specified storage. On the client side
5
+ it's recommended to use [Uppy] for asynchronous uploads.
6
6
 
7
7
  ```rb
8
8
  plugin :upload_endpoint
@@ -120,4 +120,5 @@ You can override any of the options above when creating the endpoint:
120
120
  Shrine.upload_endpoint(:cache, max_size: 20*1024*1024)
121
121
  ```
122
122
 
123
+ [upload_endpoint]: /lib/shrine/plugins/upload_endpoint.rb
123
124
  [Uppy]: https://uppy.io
@@ -1,7 +1,7 @@
1
1
  # Upload Options
2
2
 
3
- The `upload_options` plugin allows you to automatically pass additional upload
4
- options to storage on every upload:
3
+ The [`upload_options`][upload_options] plugin allows you to automatically pass
4
+ additional upload options to storage on every upload:
5
5
 
6
6
  ```rb
7
7
  plugin :upload_options, cache: { acl: "private" }
@@ -26,3 +26,5 @@ the uploader.
26
26
  ```rb
27
27
  uploader.upload(file, upload_options: { acl: "public-read" })
28
28
  ```
29
+
30
+ [upload_options]: /lib/shrine/plugins/upload_options.rb
@@ -1,7 +1,7 @@
1
1
  # Validation Helpers
2
2
 
3
- The `validation_helpers` plugin provides helper methods for validating attached
4
- files based on extracted metadata.
3
+ The [`validation_helpers`][validation_helpers] plugin provides helper methods
4
+ for validating attached files based on extracted metadata.
5
5
 
6
6
  ```rb
7
7
  plugin :validation_helpers
@@ -127,3 +127,5 @@ Attacher.validate do
127
127
  validate_mime_type_inclusion %w[image/jpeg image/png image/gif], message: "must be JPEG, PNG or GIF"
128
128
  end
129
129
  ```
130
+
131
+ [validation_helpers]: /lib/shrine/plugins/validation_helpers.rb
@@ -1,7 +1,7 @@
1
1
  # Versions
2
2
 
3
- The `versions` plugin enables your uploader to deal with versions, by allowing
4
- you to return a Hash of files when processing.
3
+ The [`versions`][versions] plugin enables your uploader to deal with versions,
4
+ by allowing you to return a Hash of files when processing.
5
5
 
6
6
  ```rb
7
7
  plugin :versions
@@ -175,5 +175,6 @@ end
175
175
  If you want to re-create a single or all versions, refer to the [reprocessing
176
176
  versions] guide for details.
177
177
 
178
+ [versions]: /lib/shrine/plugins/versions.rb
178
179
  [reprocessing versions]: /doc/regenerating_versions.md#readme
179
180
  [image_processing]: https://github.com/janko/image_processing
@@ -53,7 +53,7 @@
53
53
  host], change response headers of derivatives ([`Content-Type`],
54
54
  [`Content-Disposition`]), add [URL expiration], [cache][uploading]
55
55
  generated derivatives to a Shrine storage and more. Check out the
56
- [documentation] for more details.
56
+ [documentation][derivation_endpoint] for more details.
57
57
 
58
58
  ## Other improvements
59
59
 
@@ -74,7 +74,7 @@
74
74
  relying on multiple invocations returning the same object, you will need to
75
75
  modify your code.
76
76
 
77
- [derivation_endpoint]: /doc/plugins/derivation_endpoint.md
77
+ [derivation_endpoint]: /doc/plugins/derivation_endpoint.md#readme
78
78
  [CDN host]: /doc/plugins/derivation_endpoint.md#host
79
79
  [`Content-Type`]: /doc/plugins/derivation_endpoint.md#content-type
80
80
  [`Content-Disposition`]: /doc/plugins/derivation_endpoint.md#content-disposition
@@ -0,0 +1,52 @@
1
+ ## New Features
2
+
3
+ * The `:download_options` option has been added to the `download_endpoint`
4
+ plugin, for specifying options passed to `Storage#open`.
5
+
6
+ ```rb
7
+ plugin :download_endpoint,
8
+ download_options: {
9
+ sse_customer_algorithm: "AES256",
10
+ sse_customer_key: "secret_key",
11
+ sse_customer_key_md5: "secret_key_md5",
12
+ }
13
+ ```
14
+
15
+ * The `:upload_open_options` option has been added to the `derivation_endpoint`
16
+ plugin, for specifying options passed to `Storage#open` when downloading a
17
+ cached derivation result.
18
+
19
+ ```rb
20
+ plugin :download_endpoint,
21
+ upload: true,
22
+ upload_open_options: { response_content_encoding: "gzip" }
23
+ ```
24
+
25
+ ## Other improvements
26
+
27
+ * The `rack_response` and `derivation_endpoint` plugins now don't return any
28
+ `Content-Type` response header if the MIME type could not be determined from
29
+ the file extension. Previously it the `Content-Type` header would default to
30
+ `application/octet-stream`, which would force the browser to view the file
31
+ as generic binary content, as opposed to doing its own MIME type sniffing.
32
+
33
+ * Fixed `delete_raw` plugin breaking `derivation_endpoint` when `:upload` was
34
+ enabled.
35
+
36
+ * Fixed a few things in the `Shrine::Derivation` API:
37
+
38
+ * `Derivation#upload` doesn't close the input file anymore
39
+ * `Derivation#upload` now requires input file to respond to `#path`
40
+ * `Derivation#upload` now deletes the internally generated derivation result
41
+ * `Derivation#processed` now works when derivation result is a `File` object
42
+
43
+ * The official demo app now shows the `derivation_endpoint` plugin.
44
+
45
+ * The `#to_rack_response` method from the `rack_response` plugin now always
46
+ opens the `UploadedFile`, and does so upfront. This means if ther are any
47
+ download errors, they will bubble up from `#to_rack_response` as opposed to
48
+ when the response body is iterated over.
49
+
50
+ * When `store_dimensions` plugin was overriding `Shrine#extract_metadata`, it
51
+ made the second argument (the `context` hash) mandatory. This has been fixed,
52
+ now the second argument is optional again.
data/doc/storage/s3.md CHANGED
@@ -65,7 +65,7 @@ Shrine::Storage::S3.new(prefix: "cache", **s3_options)
65
65
  ## Upload options
66
66
 
67
67
  Sometimes you'll want to add additional upload options to all S3 uploads. You
68
- can do that by passing the `:upload` option:
68
+ can do that by passing the `:upload_options` option:
69
69
 
70
70
  ```rb
71
71
  Shrine::Storage::S3.new(upload_options: { acl: "private" }, **s3_options)
@@ -288,6 +288,6 @@ can scale exponentially by using more prefixes.
288
288
  [presigning]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_post-instance_method
289
289
  [aws-sdk-s3]: https://github.com/aws/aws-sdk-ruby/tree/master/gems/aws-sdk-s3
290
290
  [Transfer Acceleration]: http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
291
- [object lifecycle]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#put-instance_method
291
+ [object lifecycle]: http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html
292
292
  [serve private content via CloudFront]: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html
293
293
  [`Aws::CloudFront::UrlSigner`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CloudFront/UrlSigner.html
@@ -5,6 +5,8 @@ require "json"
5
5
 
6
6
  class Shrine
7
7
  module Plugins
8
+ # This is an internal plugin used by download_endpoint and
9
+ # derivation_endpoint plugins.
8
10
  module UrlsafeSerialization
9
11
  module ClassMethods
10
12
  def urlsafe_serialize(hash)
@@ -4,6 +4,9 @@ require "active_record"
4
4
 
5
5
  class Shrine
6
6
  module Plugins
7
+ # Documentation lives in [doc/plugins/activerecord.md] on GitHub.
8
+ #
9
+ # [doc/plugins/activerecord.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/activerecord.md
7
10
  module Activerecord
8
11
  def self.configure(uploader, opts = {})
9
12
  uploader.opts[:activerecord_callbacks] = opts.fetch(:callbacks, uploader.opts.fetch(:activerecord_callbacks, true))
@@ -2,6 +2,9 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
+ # Documentation lives in [doc/plugins/add_metadata.md] on GitHub.
6
+ #
7
+ # [doc/plugins/add_metadata.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/add_metadata.md
5
8
  module AddMetadata
6
9
  def self.configure(uploader)
7
10
  uploader.opts[:metadata] ||= []
@@ -2,6 +2,9 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
+ # Documentation lives in [doc/plugins/backgrounding.md] on GitHub.
6
+ #
7
+ # [doc/plugins/backgrounding.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/backgrounding.md
5
8
  module Backgrounding
6
9
  module AttacherClassMethods
7
10
  # If block is passed in, stores it to be called on promotion. Otherwise
@@ -2,6 +2,9 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
+ # Documentation lives in [doc/plugins/backup.md] on GitHub.
6
+ #
7
+ # [doc/plugins/backup.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/backup.md
5
8
  module Backup
6
9
  def self.configure(uploader, opts = {})
7
10
  uploader.opts[:backup_storage] = opts.fetch(:storage, uploader.opts[:backup_storage])
@@ -2,6 +2,9 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
+ # Documentation lives in [doc/plugins/cached_attachment_data.md] on GitHub.
6
+ #
7
+ # [doc/plugins/cached_attachment_data.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/cached_attachment_data.md
5
8
  module CachedAttachmentData
6
9
  module AttachmentMethods
7
10
  def initialize(*)
@@ -2,6 +2,9 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
+ # Documentation lives in [doc/plugins/copy.md] on GitHub.
6
+ #
7
+ # [doc/plugins/copy.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/copy.md
5
8
  module Copy
6
9
  module AttachmentMethods
7
10
  def initialize(*)
@@ -8,6 +8,9 @@ require "forwardable"
8
8
 
9
9
  class Shrine
10
10
  module Plugins
11
+ # Documentation lives in [doc/plugins/data_uri.md] on GitHub.
12
+ #
13
+ # [doc/plugins/data_uri.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/data_uri.md
11
14
  module DataUri
12
15
  class ParseError < Error; end
13
16
 
@@ -2,6 +2,9 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
+ # Documentation lives in [doc/plugins/default_storage.md] on GitHub.
6
+ #
7
+ # [doc/plugins/default_storage.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/default_storage.md
5
8
  module DefaultStorage
6
9
  def self.configure(uploader, opts = {})
7
10
  uploader.opts[:default_storage_cache] = opts.fetch(:cache, uploader.opts[:default_storage_cache])
@@ -2,6 +2,9 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
+ # Documentation lives in [doc/plugins/default_url.md] on GitHub.
6
+ #
7
+ # [doc/plugins/default_url.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/default_url.md
5
8
  module DefaultUrl
6
9
  def self.configure(uploader, &block)
7
10
  if block
@@ -2,6 +2,9 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
+ # Documentation lives in [doc/plugins/default_url_options.md] on GitHub.
6
+ #
7
+ # [doc/plugins/default_url_options.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/default_url_options.md
5
8
  module DefaultUrlOptions
6
9
  def self.configure(uploader, options = {})
7
10
  uploader.opts[:default_url_options] ||= {}
@@ -2,6 +2,9 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
+ # Documentation lives in [doc/plugins/delete_promoted.md] on GitHub.
6
+ #
7
+ # [doc/plugins/delete_promoted.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/delete_promoted.md
5
8
  module DeletePromoted
6
9
  module AttacherMethods
7
10
  def promote(uploaded_file = get, **options)
@@ -2,6 +2,9 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
+ # Documentation lives in [doc/plugins/delete_raw.md] on GitHub.
6
+ #
7
+ # [doc/plugins/delete_raw.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/delete_raw.md
5
8
  module DeleteRaw
6
9
  def self.configure(uploader, opts = {})
7
10
  uploader.opts[:delete_raw_storages] = opts.fetch(:storages, uploader.opts[:delete_raw_storages])
@@ -13,7 +16,7 @@ class Shrine
13
16
  # Deletes the file that was uploaded, unless it's an UploadedFile.
14
17
  def copy(io, context)
15
18
  super
16
- if io.respond_to?(:path) && io.path && delete_raw?
19
+ if io.respond_to?(:path) && io.path && delete_raw? && context[:delete] != false
17
20
  begin
18
21
  File.delete(io.path)
19
22
  rescue Errno::ENOENT
@@ -8,6 +8,9 @@ require "tempfile"
8
8
 
9
9
  class Shrine
10
10
  module Plugins
11
+ # Documentation lives in [doc/plugins/derivation_endpoint.md] on GitHub.
12
+ #
13
+ # [doc/plugins/derivation_endpoint.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/derivation_endpoint.md
11
14
  module DerivationEndpoint
12
15
  def self.load_dependencies(uploader, opts = {})
13
16
  uploader.plugin :rack_response
@@ -26,10 +29,17 @@ class Shrine
26
29
  end
27
30
 
28
31
  module ClassMethods
32
+ # Returns a mountable Rack app that handles derivation requests.
29
33
  def derivation_endpoint(**options)
30
34
  Shrine::DerivationEndpoint.new(shrine_class: self, options: options)
31
35
  end
32
36
 
37
+ # Calls the derivation endpoint passing the request information, and
38
+ # returns the Rack response triple.
39
+ #
40
+ # It uses a trick where it removes the derivation path prefix from the
41
+ # path info before calling the Rack app, which is what web framework
42
+ # routers do before they're calling a mounted Rack app.
33
43
  def derivation_response(env, **options)
34
44
  script_name = env["SCRIPT_NAME"]
35
45
  path_info = env["PATH_INFO"]
@@ -50,6 +60,8 @@ class Shrine
50
60
  end
51
61
  end
52
62
 
63
+ # Registers a derivation block, which is called when the corresponding
64
+ # derivation URL is requested.
53
65
  def derivation(name, &block)
54
66
  derivations[name] = block
55
67
  end
@@ -64,14 +76,23 @@ class Shrine
64
76
  end
65
77
 
66
78
  module FileMethods
79
+ # Generates a URL to a derivation with the receiver as the source file.
80
+ # Any arguments provided will be included in the URL and passed to the
81
+ # derivation block. Accepts additional URL options.
67
82
  def derivation_url(name, *args, **options)
68
83
  derivation(name, *args).url(**options)
69
84
  end
70
85
 
86
+ # Calls the specified derivation with the receiver as the source file,
87
+ # returning a Rack response triple. The derivation endpoint ultimately
88
+ # calls this method.
71
89
  def derivation_response(name, *args, env:, **options)
72
90
  derivation(name, *args, **options).response(env)
73
91
  end
74
92
 
93
+ # Returns a Shrine::Derivation object created from the provided
94
+ # arguments. This object offers additional methods for operating with
95
+ # derivatives on a lower level.
75
96
  def derivation(name, *args, **options)
76
97
  Shrine::Derivation.new(
77
98
  name: name,
@@ -99,6 +120,7 @@ class Shrine
99
120
  @options = options
100
121
  end
101
122
 
123
+ # Returns an URL to the derivation.
102
124
  def url(**options)
103
125
  Derivation::Url.new(self).call(
104
126
  host: option(:host),
@@ -110,26 +132,35 @@ class Shrine
110
132
  )
111
133
  end
112
134
 
135
+ # Returns the derivation result in form of a Rack response triple.
113
136
  def response(env)
114
137
  Derivation::Response.new(self).call(env)
115
138
  end
116
139
 
140
+ # Returns the derivation result as a File/Tempfile or a
141
+ # Shrine::UploadedFile object.
117
142
  def processed
118
143
  Derivation::Processed.new(self).call
119
144
  end
120
145
 
146
+ # Calls the derivation block and returns the direct result.
121
147
  def generate(file = nil)
122
148
  Derivation::Generate.new(self).call(file)
123
149
  end
124
150
 
151
+ # Uploads the derivation result to a dedicated destination on the specified
152
+ # Shrine storage.
125
153
  def upload(file = nil)
126
154
  Derivation::Upload.new(self).call(file)
127
155
  end
128
156
 
157
+ # Returns a Shrine::UploadedFile object pointing to the uploaded derivation
158
+ # result.
129
159
  def retrieve
130
160
  Derivation::Retrieve.new(self).call
131
161
  end
132
162
 
163
+ # Deletes the derivation result from the storage.
133
164
  def delete
134
165
  Derivation::Delete.new(self).call
135
166
  end
@@ -157,12 +188,18 @@ class Shrine
157
188
  option :type
158
189
  option :upload, default: -> { false }
159
190
  option :upload_location, default: -> { default_upload_location }, result: -> (o) { upload_location(o) }
191
+ option :upload_open_options, default: -> { {} }
160
192
  option :upload_options, default: -> { {} }
161
193
  option :upload_redirect, default: -> { false }
162
194
  option :upload_redirect_url_options, default: -> { {} }
163
195
  option :upload_storage, default: -> { source.storage_key.to_sym }
164
196
  option :version
165
197
 
198
+ # Retrieves the value of a derivation option.
199
+ #
200
+ # * If specified as a raw value, returns that value
201
+ # * If specified as a block, evaluates that it and returns the result
202
+ # * If unspecified, returns the default value
166
203
  def option(name)
167
204
  option_definition = self.class.options.fetch(name)
168
205
 
@@ -186,16 +223,21 @@ class Shrine
186
223
 
187
224
  private
188
225
 
189
- # append version and extension to upload location if specified
226
+ # When bumping the version, we also append it to the upload location to
227
+ # ensure we're not retrieving old derivatives.
190
228
  def upload_location(location)
191
229
  location = location.sub(/(?=(\.\w+)?$)/, "-#{option(:version)}") if option(:version)
192
230
  location
193
231
  end
194
232
 
233
+ # For derivation "thumbnail" with arguments "600/400" and source id of
234
+ # "1f6375ad.ext", returns "thumbnail-600-400-1f6375ad".
195
235
  def default_filename
196
236
  [name, *args, File.basename(source.id, ".*")].join("-")
197
237
  end
198
238
 
239
+ # For derivation "thumbnail" with arguments "600/400" and source id of
240
+ # "1f6375ad.ext", returns "1f6375ad/thumbnail-600-400".
199
241
  def default_upload_location
200
242
  directory = source.id.sub(/\.[^\/]+/, "")
201
243
  filename = [name, *args].join("-")
@@ -203,6 +245,7 @@ class Shrine
203
245
  [directory, filename].join("/")
204
246
  end
205
247
 
248
+ # Allows caching for 1 year or until the URL expires.
206
249
  def default_cache_control
207
250
  if option(:expires_in)
208
251
  "public, max-age=#{option(:expires_in)}"
@@ -218,6 +261,7 @@ class Shrine
218
261
  @derivation = derivation
219
262
  end
220
263
 
264
+ # Creates methods that delegate to derivation parameters.
221
265
  def self.delegate(*names)
222
266
  names.each do |name|
223
267
  protected define_method(name) {
@@ -255,14 +299,16 @@ class Shrine
255
299
  metadata: [])
256
300
 
257
301
  params = {}
258
- params[:expires_at] = (Time.now.utc + expires_in).to_i if expires_in
302
+ params[:expires_at] = (Time.now + expires_in).to_i if expires_in
259
303
  params[:version] = version if version
260
304
  params[:type] = type if type
261
305
  params[:filename] = filename if filename
262
306
  params[:disposition] = disposition if disposition
263
307
 
308
+ # serializes the source uploaded file into an URL-safe format
264
309
  source_component = source.urlsafe_dump(metadata: metadata)
265
310
 
311
+ # generate signed URL
266
312
  signed_url(name, *args, source_component, params)
267
313
  end
268
314
 
@@ -294,6 +340,15 @@ class Shrine
294
340
  [status, headers, body]
295
341
  end
296
342
 
343
+ # Verifies validity of the URL, then extracts parameters from it (such as
344
+ # derivation name, arguments and source file), and generates a derivation
345
+ # response.
346
+ #
347
+ # Returns "403 Forbidden" if signature is invalid, or if the URL has
348
+ # expired.
349
+ #
350
+ # Returns "404 Not Found" if derivation block is not defined, or if source
351
+ # file was not found on the storage.
297
352
  def handle_request(request)
298
353
  verify_signature!(request)
299
354
  check_expiry!(request)
@@ -305,12 +360,10 @@ class Shrine
305
360
 
306
361
  # request params override statically configured options
307
362
  options = self.options.dup
308
-
309
363
  options[:type] = request.params["type"] if request.params["type"]
310
364
  options[:disposition] = request.params["disposition"] if request.params["disposition"]
311
365
  options[:filename] = request.params["filename"] if request.params["filename"]
312
-
313
- options[:expires_in] = expires_in(request) if request.params["expires_at"]
366
+ options[:expires_in] = expires_in(request) if request.params["expires_at"]
314
367
 
315
368
  derivation = uploaded_file.derivation(name, *args, **options)
316
369
 
@@ -322,6 +375,7 @@ class Shrine
322
375
  error!(404, "Source file not found")
323
376
  end
324
377
 
378
+ # tell clients to cache the derivation result if it was successful
325
379
  if status == 200 || status == 206
326
380
  headers["Cache-Control"] = derivation.option(:cache_control)
327
381
  end
@@ -331,6 +385,7 @@ class Shrine
331
385
 
332
386
  private
333
387
 
388
+ # Return an error response if the signature is invalid.
334
389
  def verify_signature!(request)
335
390
  signer = UrlSigner.new(secret_key)
336
391
  signer.verify_url("#{request.path_info[1..-1]}?#{request.query_string}")
@@ -338,6 +393,7 @@ class Shrine
338
393
  error!(403, error.message.capitalize)
339
394
  end
340
395
 
396
+ # Return an error response if URL has expired.
341
397
  def check_expiry!(request)
342
398
  if request.params["expires_at"]
343
399
  error!(403, "Request has expired") if expires_in(request) <= 0
@@ -366,7 +422,8 @@ class Shrine
366
422
 
367
423
  class Derivation::Response < Derivation::Command
368
424
  delegate :type, :disposition, :filename,
369
- :upload, :upload_redirect, :upload_redirect_url_options
425
+ :upload, :upload_open_options,
426
+ :upload_redirect, :upload_redirect_url_options
370
427
 
371
428
  def call(env)
372
429
  if upload
@@ -384,27 +441,39 @@ class Shrine
384
441
  file_response(derivative, env)
385
442
  end
386
443
 
444
+ # Generates a Rack response triple from a local file using `Rack::File`.
445
+ # Fills in `Content-Type` and `Content-Disposition` response headers from
446
+ # derivation options and file extension of the derivation result.
387
447
  def file_response(file, env)
388
- file.close
389
448
  response = rack_file_response(file.path, env)
390
449
 
391
450
  status = response[0]
392
451
 
452
+ content_type = type || response[1]["Content-Type"]
453
+ content_length = response[1]["Content-Length"]
454
+ content_range = response[1]["Content-Range"]
455
+
393
456
  filename = self.filename
394
457
  filename += File.extname(file.path) if File.extname(filename).empty?
395
458
 
396
459
  headers = {}
397
- headers["Content-Type"] = type || response[1]["Content-Type"]
460
+ headers["Content-Type"] = content_type if content_type
398
461
  headers["Content-Disposition"] = content_disposition(filename)
399
- headers["Content-Length"] = response[1]["Content-Length"]
400
- headers["Content-Range"] = response[1]["Content-Range"] if response[1]["Content-Range"]
462
+ headers["Content-Length"] = content_length
463
+ headers["Content-Range"] = content_range if content_range
401
464
  headers["Accept-Ranges"] = "bytes"
402
465
 
403
466
  body = Rack::BodyProxy.new(response[2]) { File.delete(file.path) }
404
467
 
468
+ file.close
469
+
405
470
  [status, headers, body]
406
471
  end
407
472
 
473
+ # This is called when `:upload` is enabled. Checks the storage for already
474
+ # uploaded derivation result, otherwise calls the derivation block and
475
+ # uploads the result. If the derivation result is already uploaded, uses
476
+ # the `rack_response` plugin to generate a Rack response triple.
408
477
  def upload_response(env)
409
478
  uploaded_file = derivation.retrieve
410
479
 
@@ -414,7 +483,11 @@ class Shrine
414
483
  end
415
484
 
416
485
  if upload_redirect
417
- File.delete(derivative.path) if derivative
486
+ # we don't need the local derivation result here
487
+ if derivative
488
+ derivative.close
489
+ File.delete(derivative.path)
490
+ end
418
491
 
419
492
  redirect_url = uploaded_file.url(upload_redirect_url_options)
420
493
 
@@ -423,6 +496,7 @@ class Shrine
423
496
  if derivative
424
497
  file_response(derivative, env)
425
498
  else
499
+ uploaded_file.open(**upload_open_options)
426
500
  uploaded_file.to_rack_response(
427
501
  type: type,
428
502
  disposition: disposition,
@@ -433,8 +507,10 @@ class Shrine
433
507
  end
434
508
  end
435
509
 
510
+ # We call `Rack::File` with no default `Content-Type`, and make sure we
511
+ # stay compatible with both Rack 2.x and 1.6.x.
436
512
  def rack_file_response(path, env)
437
- server = Rack::File.new("", {}, "application/octet-stream")
513
+ server = Rack::File.new("", {}, nil)
438
514
 
439
515
  if Rack.release > "2"
440
516
  server.serving(Rack::Request.new(env), path)
@@ -445,6 +521,8 @@ class Shrine
445
521
  end
446
522
  end
447
523
 
524
+ # Returns disposition and filename formatted for the `Content-Disposition`
525
+ # header.
448
526
  def content_disposition(filename)
449
527
  ContentDisposition.format(disposition: disposition, filename: filename)
450
528
  end
@@ -455,29 +533,10 @@ class Shrine
455
533
 
456
534
  def call
457
535
  if upload
458
- upload_result
536
+ derivation.retrieve || derivation.upload
459
537
  else
460
- local_result
461
- end
462
- end
463
-
464
- private
465
-
466
- def local_result
467
- derivation.generate
468
- end
469
-
470
- def upload_result
471
- uploaded_file = derivation.retrieve
472
-
473
- unless uploaded_file
474
- derivative = derivation.generate
475
- uploaded_file = derivation.upload(derivative)
476
-
477
- derivative.unlink
538
+ derivation.generate
478
539
  end
479
-
480
- uploaded_file
481
540
  end
482
541
  end
483
542
 
@@ -494,6 +553,9 @@ class Shrine
494
553
 
495
554
  private
496
555
 
556
+ # Calls the derivation block with the source file and derivation arguments.
557
+ # If a file object is given, passes that as the source file, otherwise
558
+ # downloads the source uploaded file.
497
559
  def generate(file)
498
560
  if download
499
561
  with_downloaded(file) do |file|
@@ -508,6 +570,8 @@ class Shrine
508
570
  end
509
571
  end
510
572
 
573
+ # Massages the derivation result, ensuring it's opened in binary mode,
574
+ # rewinded and flushed to disk.
511
575
  def normalize(derivative)
512
576
  if derivative.is_a?(Tempfile)
513
577
  derivative.open
@@ -534,17 +598,17 @@ class Shrine
534
598
  end
535
599
  end
536
600
 
601
+ # Downloads the source uploaded file from the storage.
537
602
  def download_source
538
- download_args = download_options.any? ? [download_options] : []
539
- downloaded = false
540
-
541
- source.download(*download_args) do |file|
542
- downloaded = true
543
- yield file
603
+ begin
604
+ file = source.download(**download_options)
605
+ rescue *download_errors
606
+ raise Derivation::SourceNotFound, "source file \"#{source.id}\" was not found on storage :#{source.storage_key}"
544
607
  end
545
- rescue *download_errors
546
- raise if downloaded # re-raise if the error didn't happen on download
547
- raise Derivation::SourceNotFound, "source file \"#{source.id}\" was not found on storage :#{source.storage_key}"
608
+
609
+ yield file
610
+ ensure
611
+ file.close! if file
548
612
  end
549
613
 
550
614
  def derivation_block
@@ -559,16 +623,38 @@ class Shrine
559
623
  class Derivation::Upload < Derivation::Command
560
624
  delegate :upload_location, :upload_storage, :upload_options
561
625
 
626
+ # Uploads the derivation result to the dedicated location on the storage.
627
+ # If a file object is given, uploads that to the storage, otherwise calls
628
+ # the derivation block and uploads the result.
562
629
  def call(derivative = nil)
563
- derivative ||= derivation.generate
564
-
565
- uploader.upload derivative,
566
- location: upload_location,
567
- upload_options: upload_options
630
+ with_derivative(derivative) do |uploadable|
631
+ uploader.upload uploadable,
632
+ location: upload_location,
633
+ upload_options: upload_options,
634
+ delete: false # disable delete_raw plugin
635
+ end
568
636
  end
569
637
 
570
638
  private
571
639
 
640
+ def with_derivative(derivative)
641
+ if derivative
642
+ # we want to keep the provided file open and rewinded
643
+ File.open(derivative.path, binmode: true) do |file|
644
+ yield file
645
+ end
646
+ else
647
+ # generate the derivative and delete it afterwards
648
+ begin
649
+ file = derivation.generate
650
+ yield file
651
+ ensure
652
+ file.close
653
+ File.delete(file.path)
654
+ end
655
+ end
656
+ end
657
+
572
658
  def uploader
573
659
  shrine_class.new(upload_storage)
574
660
  end
@@ -577,6 +663,8 @@ class Shrine
577
663
  class Derivation::Retrieve < Derivation::Command
578
664
  delegate :upload_location, :upload_storage
579
665
 
666
+ # Returns a Shrine::UploadedFile object pointing to the uploaded derivation
667
+ # result it exists on the storage.
580
668
  def call
581
669
  if storage.exists?(upload_location)
582
670
  shrine_class::UploadedFile.new(
@@ -596,6 +684,7 @@ class Shrine
596
684
  class Derivation::Delete < Derivation::Command
597
685
  delegate :upload_location, :upload_storage
598
686
 
687
+ # Deletes the uploaded derivation result from the storage.
599
688
  def call
600
689
  storage.delete(upload_location)
601
690
  end
@@ -616,6 +705,8 @@ class Shrine
616
705
  @secret_key = secret_key
617
706
  end
618
707
 
708
+ # Returns a URL with the `signature` query parameter generated from the
709
+ # given path components and query parameters.
619
710
  def signed_url(*components, params)
620
711
  path = Rack::Utils.escape_path(components.join("/"))
621
712
  query = Rack::Utils.build_query(params)
@@ -627,6 +718,10 @@ class Shrine
627
718
  "#{path}?#{query}"
628
719
  end
629
720
 
721
+ # Calculcates the signature from the URL and checks whether it matches the
722
+ # value in the `signature` query parameter. Raises `InvalidSignature` if
723
+ # the `signature` parameter is missing or its value doesn't match the
724
+ # calculated signature.
630
725
  def verify_url(path_with_query)
631
726
  path, query = path_with_query.split("?")
632
727
 
@@ -645,6 +740,8 @@ class Shrine
645
740
  end
646
741
  end
647
742
 
743
+ # Uses HMAC-SHA-256 algorithm to generate a signature from the given string
744
+ # using the secret key.
648
745
  def generate_signature(string)
649
746
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, secret_key, string)
650
747
  end