shrine 2.19.3 → 3.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +523 -41
- data/LICENSE.txt +1 -1
- data/README.md +83 -979
- data/doc/advantages.md +231 -204
- data/doc/attacher.md +304 -153
- data/doc/carrierwave.md +297 -226
- data/doc/changing_derivatives.md +308 -0
- data/doc/changing_location.md +103 -21
- data/doc/changing_storage.md +110 -0
- data/doc/creating_persistence_plugins.md +132 -0
- data/doc/creating_plugins.md +43 -23
- data/doc/creating_storages.md +19 -5
- data/doc/design.md +147 -97
- data/doc/direct_s3.md +38 -28
- data/doc/external/articles.md +63 -0
- data/doc/external/extensions.md +53 -0
- data/doc/external/misc.md +32 -0
- data/doc/getting_started.md +1156 -0
- data/doc/metadata.md +190 -109
- data/doc/multiple_files.md +93 -30
- data/doc/paperclip.md +384 -262
- data/doc/plugins/activerecord.md +177 -46
- data/doc/plugins/add_metadata.md +139 -38
- data/doc/plugins/atomic_helpers.md +217 -0
- data/doc/plugins/backgrounding.md +156 -98
- data/doc/plugins/cached_attachment_data.md +7 -5
- data/doc/plugins/column.md +121 -0
- data/doc/plugins/data_uri.md +23 -22
- data/doc/plugins/default_storage.md +36 -10
- data/doc/plugins/default_url.md +30 -13
- data/doc/plugins/delete_raw.md +4 -2
- data/doc/plugins/derivation_endpoint.md +186 -101
- data/doc/plugins/derivatives.md +839 -0
- data/doc/plugins/determine_mime_type.md +4 -2
- data/doc/plugins/download_endpoint.md +64 -8
- data/doc/plugins/dynamic_storage.md +5 -3
- data/doc/plugins/entity.md +263 -0
- data/doc/plugins/form_assign.md +55 -0
- data/doc/plugins/included.md +31 -8
- data/doc/plugins/infer_extension.md +21 -10
- data/doc/plugins/instrumentation.md +38 -16
- data/doc/plugins/keep_files.md +16 -17
- data/doc/plugins/metadata_attributes.md +42 -13
- data/doc/plugins/mirroring.md +118 -0
- data/doc/plugins/model.md +210 -0
- data/doc/plugins/module_include.md +4 -2
- data/doc/plugins/multi_cache.md +24 -0
- data/doc/plugins/persistence.md +101 -0
- data/doc/plugins/presign_endpoint.md +9 -4
- data/doc/plugins/pretty_location.md +16 -3
- data/doc/plugins/processing.md +4 -2
- data/doc/plugins/rack_file.md +8 -2
- data/doc/plugins/rack_response.md +6 -2
- data/doc/plugins/recache.md +4 -2
- data/doc/plugins/refresh_metadata.md +49 -9
- data/doc/plugins/remote_url.md +84 -47
- data/doc/plugins/remove_attachment.md +27 -6
- data/doc/plugins/remove_invalid.md +21 -6
- data/doc/plugins/restore_cached_data.md +11 -3
- data/doc/plugins/sequel.md +159 -35
- data/doc/plugins/signature.md +16 -5
- data/doc/plugins/store_dimensions.md +14 -2
- data/doc/plugins/tempfile.md +4 -2
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/upload_endpoint.md +13 -13
- data/doc/plugins/upload_options.md +6 -4
- data/doc/plugins/{default_url_options.md → url_options.md} +9 -7
- data/doc/plugins/validation.md +97 -0
- data/doc/plugins/validation_helpers.md +16 -13
- data/doc/plugins/versions.md +15 -19
- data/doc/processing.md +438 -221
- data/doc/refile.md +188 -170
- data/doc/release_notes/1.0.0.md +4 -0
- data/doc/release_notes/1.1.0.md +6 -2
- data/doc/release_notes/1.2.0.md +4 -0
- data/doc/release_notes/1.3.0.md +4 -0
- data/doc/release_notes/1.4.0.md +4 -0
- data/doc/release_notes/1.4.1.md +4 -0
- data/doc/release_notes/1.4.2.md +4 -0
- data/doc/release_notes/2.0.0.md +4 -0
- data/doc/release_notes/2.0.1.md +4 -0
- data/doc/release_notes/2.1.0.md +5 -1
- data/doc/release_notes/2.1.1.md +4 -0
- data/doc/release_notes/2.10.0.md +4 -0
- data/doc/release_notes/2.10.1.md +4 -0
- data/doc/release_notes/2.11.0.md +4 -0
- data/doc/release_notes/2.12.0.md +4 -0
- data/doc/release_notes/2.13.0.md +4 -0
- data/doc/release_notes/2.14.0.md +5 -1
- data/doc/release_notes/2.15.0.md +11 -7
- data/doc/release_notes/2.16.0.md +4 -0
- data/doc/release_notes/2.17.0.md +4 -0
- data/doc/release_notes/2.18.0.md +4 -0
- data/doc/release_notes/2.19.0.md +6 -3
- data/doc/release_notes/2.2.0.md +4 -0
- data/doc/release_notes/2.3.0.md +4 -0
- data/doc/release_notes/2.3.1.md +4 -0
- data/doc/release_notes/2.4.0.md +4 -0
- data/doc/release_notes/2.4.1.md +4 -0
- data/doc/release_notes/2.5.0.md +4 -0
- data/doc/release_notes/2.6.0.md +4 -0
- data/doc/release_notes/2.6.1.md +4 -0
- data/doc/release_notes/2.7.0.md +4 -0
- data/doc/release_notes/2.8.0.md +4 -0
- data/doc/release_notes/2.9.0.md +4 -0
- data/doc/release_notes/3.0.0.md +981 -0
- data/doc/release_notes/3.0.1.md +22 -0
- data/doc/release_notes/3.1.0.md +73 -0
- data/doc/release_notes/3.2.0.md +96 -0
- data/doc/release_notes/3.2.1.md +31 -0
- data/doc/release_notes/3.2.2.md +14 -0
- data/doc/release_notes/3.3.0.md +105 -0
- data/doc/release_notes/3.4.0.md +35 -0
- data/doc/release_notes/3.5.0.md +63 -0
- data/doc/release_notes/3.6.0.md +23 -0
- data/doc/retrieving_uploads.md +5 -2
- data/doc/securing_uploads.md +60 -37
- data/doc/storage/file_system.md +20 -3
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +122 -78
- data/doc/testing.md +141 -133
- data/doc/upgrading_to_3.md +708 -0
- data/doc/validation.md +54 -90
- data/lib/shrine/attacher.rb +292 -169
- data/lib/shrine/attachment.rb +13 -46
- data/lib/shrine/plugins/_persistence.rb +93 -0
- data/lib/shrine/plugins/activerecord.rb +77 -34
- data/lib/shrine/plugins/add_metadata.rb +25 -17
- data/lib/shrine/plugins/atomic_helpers.rb +119 -0
- data/lib/shrine/plugins/backgrounding.rb +77 -113
- data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
- data/lib/shrine/plugins/column.rb +102 -0
- data/lib/shrine/plugins/data_uri.rb +38 -36
- data/lib/shrine/plugins/default_storage.rb +45 -15
- data/lib/shrine/plugins/default_url.rb +12 -24
- data/lib/shrine/plugins/default_url_options.rb +3 -30
- data/lib/shrine/plugins/delete_raw.rb +10 -16
- data/lib/shrine/plugins/derivation_endpoint.rb +130 -171
- data/lib/shrine/plugins/derivatives.rb +645 -0
- data/lib/shrine/plugins/determine_mime_type.rb +9 -21
- data/lib/shrine/plugins/download_endpoint.rb +118 -133
- data/lib/shrine/plugins/dynamic_storage.rb +5 -11
- data/lib/shrine/plugins/entity.rb +158 -0
- data/lib/shrine/plugins/form_assign.rb +108 -0
- data/lib/shrine/plugins/included.rb +6 -6
- data/lib/shrine/plugins/infer_extension.rb +17 -20
- data/lib/shrine/plugins/instrumentation.rb +59 -43
- data/lib/shrine/plugins/keep_files.rb +3 -15
- data/lib/shrine/plugins/metadata_attributes.rb +28 -19
- data/lib/shrine/plugins/mirroring.rb +142 -0
- data/lib/shrine/plugins/model.rb +160 -0
- data/lib/shrine/plugins/module_include.rb +3 -3
- data/lib/shrine/plugins/multi_cache.rb +27 -0
- data/lib/shrine/plugins/presign_endpoint.rb +27 -28
- data/lib/shrine/plugins/pretty_location.rb +15 -9
- data/lib/shrine/plugins/processing.rb +22 -9
- data/lib/shrine/plugins/rack_file.rb +2 -42
- data/lib/shrine/plugins/rack_response.rb +21 -10
- data/lib/shrine/plugins/recache.rb +6 -5
- data/lib/shrine/plugins/refresh_metadata.rb +13 -11
- data/lib/shrine/plugins/remote_url.rb +49 -49
- data/lib/shrine/plugins/remove_attachment.rb +12 -6
- data/lib/shrine/plugins/remove_invalid.rb +19 -8
- data/lib/shrine/plugins/restore_cached_data.rb +13 -7
- data/lib/shrine/plugins/sequel.rb +86 -36
- data/lib/shrine/plugins/signature.rb +10 -16
- data/lib/shrine/plugins/store_dimensions.rb +35 -40
- data/lib/shrine/plugins/tempfile.rb +1 -3
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/upload_endpoint.rb +28 -24
- data/lib/shrine/plugins/upload_options.rb +14 -15
- data/lib/shrine/plugins/url_options.rb +31 -0
- data/lib/shrine/plugins/validation.rb +80 -0
- data/lib/shrine/plugins/validation_helpers.rb +35 -58
- data/lib/shrine/plugins/versions.rb +107 -87
- data/lib/shrine/plugins.rb +22 -0
- data/lib/shrine/storage/file_system.rb +46 -64
- data/lib/shrine/storage/linter.rb +42 -7
- data/lib/shrine/storage/memory.rb +49 -0
- data/lib/shrine/storage/s3.rb +173 -160
- data/lib/shrine/uploaded_file.rb +32 -32
- data/lib/shrine/version.rb +3 -3
- data/lib/shrine.rb +87 -150
- data/shrine.gemspec +11 -12
- metadata +92 -82
- data/doc/migrating_storage.md +0 -76
- data/doc/plugins/backup.md +0 -31
- data/doc/plugins/copy.md +0 -24
- data/doc/plugins/delete_promoted.md +0 -12
- data/doc/plugins/direct_upload.md +0 -172
- data/doc/plugins/hooks.md +0 -58
- data/doc/plugins/logging.md +0 -42
- data/doc/plugins/migration_helpers.md +0 -60
- data/doc/plugins/moving.md +0 -19
- data/doc/plugins/multi_delete.md +0 -20
- data/doc/plugins/parallelize.md +0 -16
- data/doc/plugins/parsed_json.md +0 -23
- data/doc/regenerating_versions.md +0 -143
- data/lib/shrine/plugins/background_helpers.rb +0 -5
- data/lib/shrine/plugins/backup.rb +0 -90
- data/lib/shrine/plugins/copy.rb +0 -50
- data/lib/shrine/plugins/delete_promoted.rb +0 -20
- data/lib/shrine/plugins/direct_upload.rb +0 -217
- data/lib/shrine/plugins/hooks.rb +0 -90
- data/lib/shrine/plugins/logging.rb +0 -142
- data/lib/shrine/plugins/migration_helpers.rb +0 -70
- data/lib/shrine/plugins/moving.rb +0 -57
- data/lib/shrine/plugins/multi_delete.rb +0 -32
- data/lib/shrine/plugins/parallelize.rb +0 -78
- data/lib/shrine/plugins/parsed_json.rb +0 -29
|
@@ -1,113 +1,70 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
Shrine.deprecation("The versions plugin is deprecated and will be removed in Shrine 4. Use the new derivatives plugin instead.")
|
|
4
|
+
|
|
3
5
|
class Shrine
|
|
4
6
|
module Plugins
|
|
5
|
-
# Documentation
|
|
6
|
-
#
|
|
7
|
-
# [doc/plugins/versions.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/versions.md
|
|
7
|
+
# Documentation can be found on https://shrinerb.com/docs/plugins/versions
|
|
8
8
|
module Versions
|
|
9
|
-
def self.load_dependencies(uploader,
|
|
9
|
+
def self.load_dependencies(uploader, **)
|
|
10
|
+
uploader.plugin :processing
|
|
10
11
|
uploader.plugin :default_url
|
|
11
12
|
end
|
|
12
13
|
|
|
13
|
-
def self.configure(uploader, opts
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
uploader.opts[:version_names] = opts.fetch(:names, uploader.opts[:version_names])
|
|
17
|
-
uploader.opts[:version_fallbacks] = opts.fetch(:fallbacks, uploader.opts.fetch(:version_fallbacks, {}))
|
|
18
|
-
uploader.opts[:versions_fallback_to_original] = opts.fetch(:fallback_to_original, uploader.opts.fetch(:versions_fallback_to_original, true))
|
|
14
|
+
def self.configure(uploader, **opts)
|
|
15
|
+
uploader.opts[:versions] ||= { fallbacks: {}, fallback_to_original: true }
|
|
16
|
+
uploader.opts[:versions].merge!(opts)
|
|
19
17
|
end
|
|
20
18
|
|
|
21
19
|
module ClassMethods
|
|
22
|
-
def version_names
|
|
23
|
-
Shrine.deprecation("Shrine.version_names is deprecated and will be removed in Shrine 3.")
|
|
24
|
-
opts[:version_names]
|
|
25
|
-
end
|
|
26
|
-
|
|
27
20
|
def version_fallbacks
|
|
28
|
-
opts[:
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Checks that the identifier is a registered version.
|
|
32
|
-
def version?(name)
|
|
33
|
-
Shrine.deprecation("Shrine.version? is deprecated and will be removed in Shrine 3.")
|
|
34
|
-
version_names.nil? || version_names.map(&:to_s).include?(name.to_s)
|
|
21
|
+
opts[:versions][:fallbacks]
|
|
35
22
|
end
|
|
36
23
|
|
|
37
24
|
# Converts a hash of data into a hash of versions.
|
|
38
|
-
def uploaded_file(object
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
25
|
+
def uploaded_file(object)
|
|
26
|
+
object = JSON.parse(object) if object.is_a?(String)
|
|
27
|
+
|
|
28
|
+
Utils.deep_map(object, transform_keys: :to_sym) do |path, value|
|
|
29
|
+
if value.is_a?(Hash) && (value["id"].is_a?(String) || value[:id].is_a?(String))
|
|
30
|
+
file = super(value)
|
|
31
|
+
elsif value.is_a?(UploadedFile)
|
|
32
|
+
file = value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if file
|
|
36
|
+
yield file if block_given?
|
|
37
|
+
file
|
|
42
38
|
end
|
|
43
|
-
elsif object.is_a?(Array)
|
|
44
|
-
object.map { |value| uploaded_file(value, &block) }
|
|
45
|
-
else
|
|
46
|
-
super
|
|
47
39
|
end
|
|
48
40
|
end
|
|
49
41
|
end
|
|
50
42
|
|
|
51
43
|
module InstanceMethods
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if object.is_a?(Hash)
|
|
55
|
-
object.all? { |name, version| uploaded?(version) }
|
|
56
|
-
elsif object.is_a?(Array)
|
|
57
|
-
object.all? { |version| uploaded?(version) }
|
|
58
|
-
else
|
|
59
|
-
super
|
|
60
|
-
end
|
|
61
|
-
end
|
|
44
|
+
def upload(io, **options)
|
|
45
|
+
files = process(io, **options) || io
|
|
62
46
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# Stores each version individually. It asserts that all versions are
|
|
66
|
-
# known, because later the versions will be silently filtered, so
|
|
67
|
-
# we want to let the user know that they forgot to register a new
|
|
68
|
-
# version.
|
|
69
|
-
def _store(io, context)
|
|
70
|
-
if (hash = io).is_a?(Hash)
|
|
71
|
-
raise Error, ":location is not applicable to versions" if context.key?(:location)
|
|
72
|
-
raise Error, "detected multiple versions that point to the same IO object: given versions: #{hash.keys}, unique versions: #{hash.invert.invert.keys}" if hash.invert.invert != hash
|
|
73
|
-
|
|
74
|
-
hash.inject({}) do |result, (name, value)|
|
|
75
|
-
result.merge!(name.to_sym => _store(value, context.merge(version: name.to_sym){|_, v1, v2| Array(v1) + Array(v2)}))
|
|
76
|
-
end
|
|
77
|
-
elsif (array = io).is_a?(Array)
|
|
78
|
-
array.map.with_index { |value, idx| _store(value, context.merge(version: idx){|_, v1, v2| Array(v1) + Array(v2)}) }
|
|
79
|
-
else
|
|
80
|
-
super
|
|
81
|
-
end
|
|
82
|
-
end
|
|
47
|
+
Utils.map_file(files) do |name, version|
|
|
48
|
+
options.merge!(version: name.one? ? name.first : name) if name
|
|
83
49
|
|
|
84
|
-
|
|
85
|
-
def _delete(uploaded_file, context)
|
|
86
|
-
if (hash = uploaded_file).is_a?(Hash)
|
|
87
|
-
hash.each do |name, value|
|
|
88
|
-
_delete(value, context)
|
|
89
|
-
end
|
|
90
|
-
elsif (array = uploaded_file).is_a?(Array)
|
|
91
|
-
array.each do |value|
|
|
92
|
-
_delete(value, context)
|
|
93
|
-
end
|
|
94
|
-
else
|
|
95
|
-
super
|
|
50
|
+
super(version, **options, process: false)
|
|
96
51
|
end
|
|
97
52
|
end
|
|
98
53
|
end
|
|
99
54
|
|
|
100
55
|
module AttacherMethods
|
|
56
|
+
def destroy(*)
|
|
57
|
+
Utils.each_file(self.file) { |_, file| file.delete }
|
|
58
|
+
end
|
|
59
|
+
|
|
101
60
|
# Smart versioned URLs, which include the version name in the default
|
|
102
61
|
# URL, and properly forwards any options to the underlying storage.
|
|
103
62
|
def url(version = nil, **options)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if attachment.is_a?(Hash)
|
|
63
|
+
if file.is_a?(Hash)
|
|
107
64
|
if version
|
|
108
65
|
version = version.to_sym
|
|
109
|
-
if
|
|
110
|
-
|
|
66
|
+
if file.key?(version)
|
|
67
|
+
file[version].url(**options)
|
|
111
68
|
elsif fallback = shrine_class.version_fallbacks[version]
|
|
112
69
|
url(fallback, **options)
|
|
113
70
|
else
|
|
@@ -118,8 +75,8 @@ class Shrine
|
|
|
118
75
|
end
|
|
119
76
|
else
|
|
120
77
|
if version
|
|
121
|
-
if
|
|
122
|
-
|
|
78
|
+
if file && shrine_class.opts[:versions][:fallback_to_original]
|
|
79
|
+
file.url(**options)
|
|
123
80
|
else
|
|
124
81
|
default_url(**options, version: version)
|
|
125
82
|
end
|
|
@@ -129,22 +86,85 @@ class Shrine
|
|
|
129
86
|
end
|
|
130
87
|
end
|
|
131
88
|
|
|
89
|
+
# Converts the Hash/Array of UploadedFile objects into a Hash/Array of data.
|
|
90
|
+
def data
|
|
91
|
+
Utils.map_file(file, transform_keys: :to_s) do |_, version|
|
|
92
|
+
version.data
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def file=(file)
|
|
97
|
+
if file.is_a?(Hash) || file.is_a?(Array)
|
|
98
|
+
@file = file
|
|
99
|
+
else
|
|
100
|
+
super
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def uploaded_file(value, &block)
|
|
105
|
+
shrine_class.uploaded_file(value, &block)
|
|
106
|
+
end
|
|
107
|
+
|
|
132
108
|
private
|
|
133
109
|
|
|
134
|
-
def
|
|
135
|
-
|
|
110
|
+
def uploaded?(file, storage_key)
|
|
111
|
+
if file.is_a?(Hash) || file.is_a?(Array)
|
|
112
|
+
Utils.each_file(file).all? { |_, f| f.storage_key == storage_key }
|
|
113
|
+
else
|
|
114
|
+
super
|
|
115
|
+
end
|
|
136
116
|
end
|
|
117
|
+
end
|
|
137
118
|
|
|
138
|
-
|
|
139
|
-
|
|
119
|
+
module Utils
|
|
120
|
+
module_function
|
|
121
|
+
|
|
122
|
+
def each_file(object)
|
|
123
|
+
return enum_for(__method__, object) unless block_given?
|
|
124
|
+
|
|
125
|
+
map_file(object) do |path, file|
|
|
126
|
+
yield path, file
|
|
127
|
+
file
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def map_file(object, transform_keys: :to_sym)
|
|
132
|
+
if object.is_a?(Hash) || object.is_a?(Array)
|
|
133
|
+
deep_map(object, transform_keys: transform_keys) do |path, value|
|
|
134
|
+
yield path, value unless value.is_a?(Hash) || value.is_a?(Array)
|
|
135
|
+
end
|
|
136
|
+
elsif object
|
|
137
|
+
yield nil, object
|
|
138
|
+
else
|
|
139
|
+
object
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def deep_map(object, path = [], transform_keys:, &block)
|
|
140
144
|
if object.is_a?(Hash)
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
result = yield path, object
|
|
146
|
+
|
|
147
|
+
return result if result
|
|
148
|
+
|
|
149
|
+
object.inject({}) do |hash, (key, value)|
|
|
150
|
+
key = key.send(transform_keys)
|
|
151
|
+
result = yield [*path, key], value
|
|
152
|
+
|
|
153
|
+
hash.merge! key => (result || deep_map(value, [*path, key], transform_keys: transform_keys, &block))
|
|
143
154
|
end
|
|
144
155
|
elsif object.is_a?(Array)
|
|
145
|
-
|
|
156
|
+
result = yield path, object
|
|
157
|
+
|
|
158
|
+
return result if result
|
|
159
|
+
|
|
160
|
+
object.map.with_index do |value, idx|
|
|
161
|
+
result = yield [*path, idx], value
|
|
162
|
+
|
|
163
|
+
result || deep_map(value, [*path, idx], transform_keys: transform_keys, &block)
|
|
164
|
+
end
|
|
146
165
|
else
|
|
147
|
-
|
|
166
|
+
result = yield path, object
|
|
167
|
+
result or fail Shrine::Error, "leaf reached"
|
|
148
168
|
end
|
|
149
169
|
end
|
|
150
170
|
end
|
data/lib/shrine/plugins.rb
CHANGED
|
@@ -18,6 +18,28 @@ class Shrine
|
|
|
18
18
|
plugin
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
# Delegate call to the plugin in a way that works across Ruby versions.
|
|
22
|
+
def self.load_dependencies(plugin, uploader, *args, **kwargs, &block)
|
|
23
|
+
return unless plugin.respond_to?(:load_dependencies)
|
|
24
|
+
|
|
25
|
+
if kwargs.any?
|
|
26
|
+
plugin.load_dependencies(uploader, *args, **kwargs, &block)
|
|
27
|
+
else
|
|
28
|
+
plugin.load_dependencies(uploader, *args, &block)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Delegate call to the plugin in a way that works across Ruby versions.
|
|
33
|
+
def self.configure(plugin, uploader, *args, **kwargs, &block)
|
|
34
|
+
return unless plugin.respond_to?(:configure)
|
|
35
|
+
|
|
36
|
+
if kwargs.any?
|
|
37
|
+
plugin.configure(uploader, *args, **kwargs, &block)
|
|
38
|
+
else
|
|
39
|
+
plugin.configure(uploader, *args, &block)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
21
43
|
# Register the given plugin with Shrine, so that it can be loaded using
|
|
22
44
|
# `Shrine.plugin` with a symbol. Should be used by plugin files. Example:
|
|
23
45
|
#
|
|
@@ -6,7 +6,7 @@ require "pathname"
|
|
|
6
6
|
class Shrine
|
|
7
7
|
module Storage
|
|
8
8
|
class FileSystem
|
|
9
|
-
attr_reader :directory, :prefix, :
|
|
9
|
+
attr_reader :directory, :prefix, :permissions, :directory_permissions
|
|
10
10
|
|
|
11
11
|
# Initializes a storage for uploading to the filesystem.
|
|
12
12
|
#
|
|
@@ -28,9 +28,7 @@ class Shrine
|
|
|
28
28
|
# : By default empty folders inside the directory are automatically
|
|
29
29
|
# deleted, but if it happens that it causes too much load on the
|
|
30
30
|
# filesystem, you can set this option to `false`.
|
|
31
|
-
def initialize(directory, prefix: nil,
|
|
32
|
-
Shrine.deprecation("The :host option to Shrine::Storage::FileSystem#initialize is deprecated and will be removed in Shrine 3. Pass :host to FileSystem#url instead, you can also use default_url_options plugin.") if host
|
|
33
|
-
|
|
31
|
+
def initialize(directory, prefix: nil, clean: true, permissions: 0644, directory_permissions: 0755)
|
|
34
32
|
if prefix
|
|
35
33
|
@prefix = Pathname(relative(prefix))
|
|
36
34
|
@directory = Pathname(directory).join(@prefix).expand_path
|
|
@@ -38,7 +36,6 @@ class Shrine
|
|
|
38
36
|
@directory = Pathname(directory).expand_path
|
|
39
37
|
end
|
|
40
38
|
|
|
41
|
-
@host = host
|
|
42
39
|
@permissions = permissions
|
|
43
40
|
@directory_permissions = directory_permissions
|
|
44
41
|
@clean = clean
|
|
@@ -51,39 +48,21 @@ class Shrine
|
|
|
51
48
|
|
|
52
49
|
# Copies the file into the given location.
|
|
53
50
|
def upload(io, id, move: false, **)
|
|
54
|
-
if move && movable?(io
|
|
55
|
-
move(io, id)
|
|
51
|
+
if move && movable?(io)
|
|
52
|
+
move(io, path!(id))
|
|
56
53
|
else
|
|
57
54
|
IO.copy_stream(io, path!(id))
|
|
58
|
-
|
|
59
|
-
path(id).chmod(permissions) if permissions
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Moves the file to the given location. This gets called by the `moving`
|
|
64
|
-
# plugin.
|
|
65
|
-
def move(io, id, **)
|
|
66
|
-
if io.respond_to?(:path)
|
|
67
|
-
FileUtils.mv io.path, path!(id)
|
|
68
|
-
else
|
|
69
|
-
FileUtils.mv io.storage.path(io.id), path!(id)
|
|
70
|
-
io.storage.clean(io.storage.path(io.id)) if io.storage.clean?
|
|
71
55
|
end
|
|
72
56
|
|
|
73
57
|
path(id).chmod(permissions) if permissions
|
|
74
58
|
end
|
|
75
59
|
|
|
76
|
-
# Returns true if the file is a `File` or a UploadedFile uploaded by the
|
|
77
|
-
# FileSystem storage.
|
|
78
|
-
def movable?(io, id)
|
|
79
|
-
io.respond_to?(:path) ||
|
|
80
|
-
(io.is_a?(UploadedFile) && io.storage.is_a?(Storage::FileSystem))
|
|
81
|
-
end
|
|
82
|
-
|
|
83
60
|
# Opens the file on the given location in read mode. Accepts additional
|
|
84
61
|
# `File.open` arguments.
|
|
85
|
-
def open(id, **options
|
|
86
|
-
path(id).open(binmode: true, **options
|
|
62
|
+
def open(id, **options)
|
|
63
|
+
path(id).open(binmode: true, **options)
|
|
64
|
+
rescue Errno::ENOENT
|
|
65
|
+
raise Shrine::FileNotFound, "file #{id.inspect} not found on storage"
|
|
87
66
|
end
|
|
88
67
|
|
|
89
68
|
# Returns true if the file exists on the filesystem.
|
|
@@ -91,6 +70,16 @@ class Shrine
|
|
|
91
70
|
path(id).exist?
|
|
92
71
|
end
|
|
93
72
|
|
|
73
|
+
# If #prefix is not present, returns a path composed of #directory and
|
|
74
|
+
# the given `id`. If #prefix is present, it excludes the #directory part
|
|
75
|
+
# from the returned path (e.g. #directory can be set to "public" folder).
|
|
76
|
+
# Both cases accept a `:host` value which will be prefixed to the
|
|
77
|
+
# generated path.
|
|
78
|
+
def url(id, host: nil, **options)
|
|
79
|
+
path = (prefix ? relative_path(id) : path(id)).to_s
|
|
80
|
+
host ? host + path : path
|
|
81
|
+
end
|
|
82
|
+
|
|
94
83
|
# Delets the file, and by default deletes the containing directory if
|
|
95
84
|
# it's empty.
|
|
96
85
|
def delete(id)
|
|
@@ -100,14 +89,11 @@ class Shrine
|
|
|
100
89
|
rescue Errno::ENOENT
|
|
101
90
|
end
|
|
102
91
|
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def url(id, host: self.host, **options)
|
|
109
|
-
path = (prefix ? relative_path(id) : path(id)).to_s
|
|
110
|
-
host ? host + path : path
|
|
92
|
+
# Deletes the specified directory on the filesystem.
|
|
93
|
+
#
|
|
94
|
+
# file_system.delete_prefixed("somekey/derivatives/")
|
|
95
|
+
def delete_prefixed(delete_prefix)
|
|
96
|
+
FileUtils.rm_rf directory.join(delete_prefix)
|
|
111
97
|
end
|
|
112
98
|
|
|
113
99
|
# Deletes all files from the #directory. If a block is passed in, deletes
|
|
@@ -115,15 +101,10 @@ class Shrine
|
|
|
115
101
|
#
|
|
116
102
|
# file_system.clear! # deletes all files and subdirectories in the storage directory
|
|
117
103
|
# file_system.clear! { |path| path.mtime < Time.now - 7*24*60*60 } # deletes only files older than 1 week
|
|
118
|
-
def clear!(
|
|
119
|
-
if
|
|
104
|
+
def clear!(&condition)
|
|
105
|
+
if condition
|
|
120
106
|
list_files(directory) do |path|
|
|
121
|
-
|
|
122
|
-
Shrine.deprecation("The :older_than option to FileSystem#clear! is deprecated and will be removed in Shrine 3. You should use a block instead, e.g. `storage.clear! { |path| path.mtime < Time.now - 7*24*60*60 }`.")
|
|
123
|
-
next unless path.mtime < older_than
|
|
124
|
-
else
|
|
125
|
-
next unless condition.call(path)
|
|
126
|
-
end
|
|
107
|
+
next unless condition.call(path)
|
|
127
108
|
path.delete
|
|
128
109
|
clean(path) if clean?
|
|
129
110
|
end
|
|
@@ -137,15 +118,6 @@ class Shrine
|
|
|
137
118
|
directory.join(id.gsub("/", File::SEPARATOR))
|
|
138
119
|
end
|
|
139
120
|
|
|
140
|
-
# Catches the deprecated `#download` method.
|
|
141
|
-
def method_missing(name, *args, &block)
|
|
142
|
-
case name
|
|
143
|
-
when :download then deprecated_download(*args, &block)
|
|
144
|
-
else
|
|
145
|
-
super
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
121
|
protected
|
|
150
122
|
|
|
151
123
|
# Cleans all empty subdirectories up the hierarchy.
|
|
@@ -165,6 +137,24 @@ class Shrine
|
|
|
165
137
|
|
|
166
138
|
private
|
|
167
139
|
|
|
140
|
+
# Moves the file to the given location. This gets called by the `moving`
|
|
141
|
+
# plugin.
|
|
142
|
+
def move(io, path)
|
|
143
|
+
if io.respond_to?(:path)
|
|
144
|
+
FileUtils.mv io.path, path
|
|
145
|
+
else
|
|
146
|
+
FileUtils.mv io.storage.path(io.id), path
|
|
147
|
+
io.storage.clean(io.storage.path(io.id)) if io.storage.clean?
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Returns true if the file is a `File` or a UploadedFile uploaded by the
|
|
152
|
+
# FileSystem storage.
|
|
153
|
+
def movable?(io)
|
|
154
|
+
io.respond_to?(:path) ||
|
|
155
|
+
(io.is_a?(UploadedFile) && io.storage.is_a?(Storage::FileSystem))
|
|
156
|
+
end
|
|
157
|
+
|
|
168
158
|
# Creates all intermediate directories for that location.
|
|
169
159
|
def path!(id)
|
|
170
160
|
path = path(id)
|
|
@@ -191,20 +181,12 @@ class Shrine
|
|
|
191
181
|
Dir.empty?(path)
|
|
192
182
|
end
|
|
193
183
|
else
|
|
184
|
+
# :nocov:
|
|
194
185
|
def dir_empty?(path)
|
|
195
186
|
Dir.foreach(path) { |x| return false unless [".", ".."].include?(x) }
|
|
196
187
|
true
|
|
197
188
|
end
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def deprecated_download(id, **options)
|
|
201
|
-
Shrine.deprecation("Shrine::Storage::FileSystem#download is deprecated and will be removed in Shrine 3.")
|
|
202
|
-
tempfile = Tempfile.new(["shrine-filesystem", File.extname(id)], binmode: true)
|
|
203
|
-
open(id, **options) { |file| IO.copy_stream(file, tempfile) }
|
|
204
|
-
tempfile.tap(&:open)
|
|
205
|
-
rescue
|
|
206
|
-
tempfile.close! if tempfile
|
|
207
|
-
raise
|
|
189
|
+
# :nocov:
|
|
208
190
|
end
|
|
209
191
|
end
|
|
210
192
|
end
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
1
|
require "shrine"
|
|
4
2
|
|
|
5
3
|
require "forwardable"
|
|
@@ -30,19 +28,32 @@ class Shrine
|
|
|
30
28
|
new(*args).call
|
|
31
29
|
end
|
|
32
30
|
|
|
33
|
-
def initialize(storage, action: :error)
|
|
34
|
-
@storage
|
|
35
|
-
@action
|
|
31
|
+
def initialize(storage, action: :error, nonexisting: "nonexisting")
|
|
32
|
+
@storage = storage
|
|
33
|
+
@action = action
|
|
34
|
+
@nonexisting = nonexisting
|
|
36
35
|
end
|
|
37
36
|
|
|
38
37
|
def call(io_factory = default_io_factory)
|
|
39
|
-
storage.upload(io_factory.call, id = "foo"
|
|
38
|
+
storage.upload(io_factory.call, id = "foo", shrine_metadata: { "foo" => "bar" })
|
|
40
39
|
|
|
41
40
|
lint_open(id)
|
|
42
41
|
lint_exists(id)
|
|
43
42
|
lint_url(id)
|
|
44
43
|
lint_delete(id)
|
|
45
44
|
|
|
45
|
+
if storage.respond_to?(:delete_prefixed)
|
|
46
|
+
storage.upload(io_factory.call, id1 = "a/a/a")
|
|
47
|
+
storage.upload(io_factory.call, id2 = "a/a/b")
|
|
48
|
+
storage.upload(io_factory.call, id3 = "a/aaa/a")
|
|
49
|
+
|
|
50
|
+
lint_delete_prefixed(prefix: "a/a/",
|
|
51
|
+
expect_deleted: [id1, id2],
|
|
52
|
+
expect_remaining: [id3])
|
|
53
|
+
|
|
54
|
+
storage.delete(id3)
|
|
55
|
+
end
|
|
56
|
+
|
|
46
57
|
if storage.respond_to?(:clear!)
|
|
47
58
|
storage.upload(io_factory.call, id = "quux".dup)
|
|
48
59
|
lint_clear(id)
|
|
@@ -51,6 +62,8 @@ class Shrine
|
|
|
51
62
|
if storage.respond_to?(:presign)
|
|
52
63
|
lint_presign(id)
|
|
53
64
|
end
|
|
65
|
+
|
|
66
|
+
true
|
|
54
67
|
end
|
|
55
68
|
|
|
56
69
|
def lint_open(id)
|
|
@@ -58,6 +71,14 @@ class Shrine
|
|
|
58
71
|
error :open, "doesn't return a valid IO object" if !io?(opened)
|
|
59
72
|
error :open, "returns an empty IO object" if opened.read.empty?
|
|
60
73
|
opened.close
|
|
74
|
+
|
|
75
|
+
begin
|
|
76
|
+
storage.open(@nonexisting)
|
|
77
|
+
error :open, "should raise an exception on nonexisting file"
|
|
78
|
+
rescue Shrine::FileNotFound
|
|
79
|
+
rescue => exception
|
|
80
|
+
error :open, "should raise Shrine::FileNotFound on nonexisting file"
|
|
81
|
+
end
|
|
61
82
|
end
|
|
62
83
|
|
|
63
84
|
def lint_exists(id)
|
|
@@ -86,12 +107,26 @@ class Shrine
|
|
|
86
107
|
end
|
|
87
108
|
|
|
88
109
|
def lint_presign(id)
|
|
89
|
-
data = storage.presign(id
|
|
110
|
+
data = storage.presign(id)
|
|
90
111
|
error :presign, "result should be a Hash" unless data.respond_to?(:to_h)
|
|
91
112
|
error :presign, "result should include :method key" unless data.to_h.key?(:method)
|
|
92
113
|
error :presign, "result should include :url key" unless data.to_h.key?(:url)
|
|
93
114
|
end
|
|
94
115
|
|
|
116
|
+
def lint_delete_prefixed(prefix:, expect_deleted:, expect_remaining:)
|
|
117
|
+
storage.delete_prefixed(prefix)
|
|
118
|
+
|
|
119
|
+
expect_deleted.each do |key|
|
|
120
|
+
next unless storage.exists?(key)
|
|
121
|
+
error :delete_prefixed, "#{key} still #exists? after #clear_prefix('a/a/')"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
expect_remaining.each do |key|
|
|
125
|
+
next if storage.exists?(key)
|
|
126
|
+
error :delete_prefixed, "#{key} doesn't #exists? but should after #clear_prefix('a/a/')"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
95
130
|
private
|
|
96
131
|
|
|
97
132
|
attr_reader :storage
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shrine"
|
|
4
|
+
require "stringio"
|
|
5
|
+
|
|
6
|
+
class Shrine
|
|
7
|
+
module Storage
|
|
8
|
+
class Memory
|
|
9
|
+
attr_reader :store
|
|
10
|
+
|
|
11
|
+
def initialize(store = {})
|
|
12
|
+
@store = store
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def upload(io, id, **)
|
|
16
|
+
store[id] = io.read
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def open(id, **)
|
|
20
|
+
io = StringIO.new(store.fetch(id))
|
|
21
|
+
io.set_encoding(io.string.encoding) # Ruby 2.7.0 – https://bugs.ruby-lang.org/issues/16497
|
|
22
|
+
io
|
|
23
|
+
rescue KeyError
|
|
24
|
+
raise Shrine::FileNotFound, "file #{id.inspect} not found on storage"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def exists?(id)
|
|
28
|
+
store.key?(id)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def url(id, *)
|
|
32
|
+
"memory://#{id}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def delete(id)
|
|
36
|
+
store.delete(id)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def delete_prefixed(delete_prefix)
|
|
40
|
+
delete_prefix = delete_prefix.chomp("/") + "/"
|
|
41
|
+
store.delete_if { |key, _value| key.start_with?(delete_prefix) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def clear!
|
|
45
|
+
store.clear
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|