resizing 1.2.2 → 1.3.1
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/.devcontainer/Dockerfile +73 -0
- data/.devcontainer/compose.yaml +58 -0
- data/.devcontainer/devcontainer-lock.json +9 -0
- data/.devcontainer/devcontainer.json +32 -0
- data/.devcontainer/post-create.sh +41 -0
- data/.github/copilot-instructions.md +1 -0
- data/.github/labeler.yml +28 -0
- data/.github/release.yml +35 -0
- data/.github/workflows/labeler.yml +20 -0
- data/.github/workflows/lint.yml +37 -0
- data/.github/workflows/test.yml +16 -9
- data/AGENTS.md +1 -0
- data/CHANGELOG.md +3 -0
- data/CLAUDE.md +68 -0
- data/Gemfile +9 -3
- data/README.md +114 -2
- data/Rakefile +5 -7
- data/bin/setup +3 -0
- data/git-hooks/pre-push +6 -0
- data/git-hooks/pre-push-ruby.sh +48 -0
- data/lib/resizing/carrier_wave/callback_file.rb +88 -0
- data/lib/resizing/carrier_wave/storage/file.rb +84 -36
- data/lib/resizing/carrier_wave/storage/remote.rb +7 -3
- data/lib/resizing/carrier_wave.rb +41 -6
- data/lib/resizing/client.rb +0 -1
- data/lib/resizing/configurable.rb +1 -1
- data/lib/resizing/configuration.rb +40 -10
- data/lib/resizing/mock_client.rb +23 -7
- data/lib/resizing/public_id.rb +3 -2
- data/lib/resizing/version.rb +1 -1
- data/lib/resizing.rb +0 -1
- data/resizing.gemspec +3 -3
- data/test/resizing/carrier_wave/storage/file_test.rb +455 -9
- data/test/resizing/carrier_wave/storage/remote_test.rb +91 -9
- data/test/resizing/carrier_wave_cache_callbacks_test.rb +224 -0
- data/test/resizing/carrier_wave_callback_file_test.rb +77 -0
- data/test/resizing/carrier_wave_test.rb +201 -26
- data/test/resizing/carrier_wave_uploader_test.rb +211 -0
- data/test/resizing/client_test.rb +324 -25
- data/test/resizing/configuration_test.rb +235 -10
- data/test/resizing/http_clientable_test.rb +27 -0
- data/test/resizing/mock_client_test.rb +111 -1
- data/test/resizing/public_id_test.rb +96 -0
- data/test/resizing_module_test.rb +110 -0
- data/test/resizing_test.rb +8 -0
- data/test/test_helper.rb +297 -9
- metadata +33 -9
data/Rakefile
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'bundler/gem_tasks'
|
|
4
4
|
require 'rake/testtask'
|
|
5
|
-
require '
|
|
5
|
+
require 'rubocop/rake_task'
|
|
6
6
|
|
|
7
7
|
Rake::TestTask.new(:test) do |t|
|
|
8
8
|
t.libs << 'test'
|
|
@@ -10,11 +10,9 @@ Rake::TestTask.new(:test) do |t|
|
|
|
10
10
|
t.test_files = FileList['test/**/*_test.rb']
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
config.future_release = Resizing::VERSION
|
|
18
|
-
end
|
|
13
|
+
RuboCop::RakeTask.new
|
|
14
|
+
|
|
15
|
+
desc 'Run the same checks as CI (RuboCop and the tests)'
|
|
16
|
+
task ci: %i[rubocop test]
|
|
19
17
|
|
|
20
18
|
task default: :test
|
data/bin/setup
CHANGED
data/git-hooks/pre-push
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# pre-push-ruby.sh
|
|
3
|
+
# Runs the same checks as CI (RuboCop and the test suite) before pushing.
|
|
4
|
+
# Called from git-hooks/pre-push. Aborts the push if any check fails.
|
|
5
|
+
#
|
|
6
|
+
# The checks run wherever the gems are installed: the current shell when it
|
|
7
|
+
# already has them (inside the devcontainer, or a host with `bundle install`
|
|
8
|
+
# done), otherwise the devcontainer via its CLI or docker compose.
|
|
9
|
+
#
|
|
10
|
+
# PRE_PUSH_SKIP_TESTS=1 git push # lint only, skip the test suite
|
|
11
|
+
# git push --no-verify # skip every check (not recommended)
|
|
12
|
+
ROOT="$(git rev-parse --show-toplevel)"
|
|
13
|
+
cd "$ROOT" || exit 1
|
|
14
|
+
|
|
15
|
+
if bundle check >/dev/null 2>&1; then
|
|
16
|
+
MODE=local
|
|
17
|
+
elif command -v devcontainer >/dev/null 2>&1; then
|
|
18
|
+
MODE=devcontainer
|
|
19
|
+
elif docker compose -f .devcontainer/compose.yaml ps --status running --quiet app 2>/dev/null | grep -q .; then
|
|
20
|
+
MODE=compose
|
|
21
|
+
else
|
|
22
|
+
echo "No environment with the gems installed was found."
|
|
23
|
+
echo "Run 'devcontainer up --workspace-folder .' (or 'bundle install') first."
|
|
24
|
+
echo "To skip this check (not recommended): git push --no-verify"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
run() {
|
|
29
|
+
case "$MODE" in
|
|
30
|
+
local) bundle exec "$@" ;;
|
|
31
|
+
devcontainer) devcontainer exec --workspace-folder "$ROOT" bundle exec "$@" ;;
|
|
32
|
+
compose) docker compose -f .devcontainer/compose.yaml exec -T app bundle exec "$@" ;;
|
|
33
|
+
esac
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if [ -n "${PRE_PUSH_SKIP_TESTS:-}" ]; then
|
|
37
|
+
echo "Running RuboCop ($MODE)..."
|
|
38
|
+
run rake rubocop
|
|
39
|
+
else
|
|
40
|
+
echo "Running RuboCop and tests ($MODE)..."
|
|
41
|
+
run rake ci
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
if [ $? -ne 0 ]; then
|
|
45
|
+
echo "Pre-push checks failed. Push aborted."
|
|
46
|
+
echo "To skip this check (not recommended): git push --no-verify"
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Resizing
|
|
4
|
+
module CarrierWave
|
|
5
|
+
# The file handed to CarrierWave's :cache callbacks (extension_allowlist /
|
|
6
|
+
# extension_denylist / content_type_allowlist / content_type_denylist / size_range).
|
|
7
|
+
#
|
|
8
|
+
# The extension used by the callbacks is derived from the content type whenever the
|
|
9
|
+
# content type is known, instead of being taken from the file name as plain
|
|
10
|
+
# CarrierWave does.
|
|
11
|
+
#
|
|
12
|
+
# A file downloaded from a Resizing URL (e.g. when it is assigned through
|
|
13
|
+
# `remote_<column>_url=`) is the typical case: the last segment of the URL is the
|
|
14
|
+
# version string (`.../images/<image_id>/v<version>`), which contains dots, so the
|
|
15
|
+
# "extension" CarrierWave finds in the file name is a meaningless part of the version
|
|
16
|
+
# (`Fv_Q` for `/v0cRq7Yb2mZt9LxA3wN8eKd5pHs1.Fv_Q`), and extension_allowlist would
|
|
17
|
+
# reject every such file. The HTTP response carries the real content type, so it is
|
|
18
|
+
# the reliable source.
|
|
19
|
+
#
|
|
20
|
+
# When the content type is unknown (`application/octet-stream`, or a type that no
|
|
21
|
+
# extension is registered for), the file name is used as is, as in plain CarrierWave.
|
|
22
|
+
class CallbackFile < ::CarrierWave::SanitizedFile
|
|
23
|
+
# Content type given to files whose real type could not be determined.
|
|
24
|
+
# Carries no information about the extension, so it is treated as unknown.
|
|
25
|
+
UNKNOWN_CONTENT_TYPE = 'application/octet-stream'
|
|
26
|
+
|
|
27
|
+
def original_filename
|
|
28
|
+
name = super
|
|
29
|
+
return name if name.nil?
|
|
30
|
+
|
|
31
|
+
extension = extension_for_content_type
|
|
32
|
+
return name if extension.nil?
|
|
33
|
+
|
|
34
|
+
basename, current_extension = split_extension(name)
|
|
35
|
+
return name if extensions_for_content_type.include?(current_extension.downcase)
|
|
36
|
+
|
|
37
|
+
"#{basename}.#{extension}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# The preferred extension of the content type, or nil when the content type is unknown.
|
|
43
|
+
def extension_for_content_type
|
|
44
|
+
extensions_for_content_type.first
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Every extension registered for the content type (lower case), or [] when unknown.
|
|
48
|
+
# Neither library raises on an unregistered or malformed content type: MIME::Types
|
|
49
|
+
# returns an empty list and MiniMime returns nil.
|
|
50
|
+
#
|
|
51
|
+
# MIME::Types (a dependency of CarrierWave 1.x and of fog) is preferred because it
|
|
52
|
+
# knows every alias of an extension (jpeg / jpg / jpe for image/jpeg); MiniMime
|
|
53
|
+
# (a dependency of CarrierWave 2.x+) knows only one extension per content type.
|
|
54
|
+
def extensions_for_content_type
|
|
55
|
+
@extensions_for_content_type ||= begin
|
|
56
|
+
# Drop the parameters (`image/jpeg; charset=binary`), which MiniMime cannot handle
|
|
57
|
+
type = content_type.to_s.split(';').first.to_s.strip.downcase
|
|
58
|
+
if type.empty? || type == UNKNOWN_CONTENT_TYPE
|
|
59
|
+
[]
|
|
60
|
+
else
|
|
61
|
+
lookup_extensions(type)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def lookup_extensions(type)
|
|
67
|
+
if mime_types_available?
|
|
68
|
+
::MIME::Types[type].flat_map(&:extensions).map(&:downcase).uniq
|
|
69
|
+
elsif mini_mime_available?
|
|
70
|
+
extension = ::MiniMime.lookup_by_content_type(type)&.extension
|
|
71
|
+
extension.nil? ? [] : [extension.downcase]
|
|
72
|
+
else
|
|
73
|
+
[]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Which lookup library is loaded depends on the application (CarrierWave 1.x loads
|
|
78
|
+
# MIME::Types, 2.x+ loads MiniMime); the tests stub these to exercise each path.
|
|
79
|
+
def mime_types_available?
|
|
80
|
+
!defined?(::MIME::Types).nil?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def mini_mime_available?
|
|
84
|
+
!defined?(::MiniMime).nil?
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -7,26 +7,37 @@ module Resizing
|
|
|
7
7
|
class File
|
|
8
8
|
include ::CarrierWave::Utilities::Uri
|
|
9
9
|
|
|
10
|
+
# Fallback content type used when the type cannot be determined from the file name.
|
|
11
|
+
DEFAULT_CONTENT_TYPE = 'application/octet-stream'
|
|
12
|
+
|
|
10
13
|
attr_reader :public_id
|
|
11
14
|
|
|
12
15
|
def initialize(uploader, identifier = nil)
|
|
13
16
|
@uploader = uploader
|
|
14
17
|
@content_type = nil
|
|
15
18
|
@public_id = Resizing::PublicId.new(identifier)
|
|
16
|
-
@file = nil
|
|
17
19
|
@metadata = nil
|
|
20
|
+
@metadata_fetched = false
|
|
18
21
|
end
|
|
19
22
|
|
|
23
|
+
# Metadata of the stored image, as returned by the Resizing metadata API.
|
|
24
|
+
#
|
|
25
|
+
# === Returns
|
|
26
|
+
#
|
|
27
|
+
# [Hash, nil] metadata hash, or nil when the image is unknown/unreachable
|
|
20
28
|
def attributes
|
|
21
|
-
|
|
29
|
+
metadata
|
|
22
30
|
end
|
|
23
31
|
|
|
24
32
|
def authenticated_url(_options = {})
|
|
25
33
|
nil
|
|
26
34
|
end
|
|
27
35
|
|
|
36
|
+
# NOTE: Called through CarrierWave's Uploader::Proxy#content_type, so it must not
|
|
37
|
+
# raise. Falls back to the metadata API when the content type is not known locally
|
|
38
|
+
# (e.g. the model was reloaded from the DB), and returns nil if it cannot be fetched.
|
|
28
39
|
def content_type
|
|
29
|
-
@content_type
|
|
40
|
+
@content_type ||= metadata && metadata['content_type']
|
|
30
41
|
end
|
|
31
42
|
|
|
32
43
|
# rubocop:disable Metrics/AbcSize
|
|
@@ -73,29 +84,25 @@ module Resizing
|
|
|
73
84
|
##
|
|
74
85
|
# Read content of file from service
|
|
75
86
|
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
87
|
+
# NOTE: Resizing keeps no local copy of the stored image and this gem has no API to
|
|
88
|
+
# download the binary (Resizing::Client#get is not implemented either), so reading the
|
|
89
|
+
# content would require a new download API. It is not called by the regular CarrierWave
|
|
90
|
+
# flow (only through Uploader::Proxy#read, i.e. explicitly by the application), so it
|
|
91
|
+
# raises instead of silently returning nil.
|
|
79
92
|
def read
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return if file_body.nil?
|
|
83
|
-
return file_body unless file_body.is_a?(::File)
|
|
84
|
-
|
|
85
|
-
# Fog::Storage::XXX::File#body could return the source file which was upoloaded to the remote server.
|
|
86
|
-
read_source_file(file_body) if ::File.exist?(file_body.path)
|
|
87
|
-
|
|
88
|
-
# If the source file doesn't exist, the remote content is read
|
|
89
|
-
@file = nil
|
|
90
|
-
file.body
|
|
93
|
+
raise NotImplementedError, 'Resizing does not support reading the stored image content'
|
|
91
94
|
end
|
|
92
95
|
|
|
96
|
+
# NOTE: Called through CarrierWave's Uploader::Proxy#size, so it must not raise.
|
|
97
|
+
# The size is only known to the Resizing service, so it comes from the metadata API.
|
|
98
|
+
# Returns 0 when it cannot be fetched, following Uploader::Proxy#size, which itself
|
|
99
|
+
# falls back to 0, so that the return value is always an Integer.
|
|
93
100
|
def size
|
|
94
|
-
|
|
101
|
+
(metadata && metadata['size']) || 0
|
|
95
102
|
end
|
|
96
103
|
|
|
97
104
|
def exists?
|
|
98
|
-
|
|
105
|
+
!metadata.nil?
|
|
99
106
|
end
|
|
100
107
|
|
|
101
108
|
def current_path
|
|
@@ -118,8 +125,10 @@ module Resizing
|
|
|
118
125
|
@content_type ||= if new_file.respond_to? :content_type
|
|
119
126
|
new_file.content_type
|
|
120
127
|
else
|
|
121
|
-
#
|
|
122
|
-
|
|
128
|
+
# Guess content-type from extension. MIME::Types returns an empty
|
|
129
|
+
# list for extensions it does not know (e.g. `.url`), so fall back
|
|
130
|
+
# to the generic binary type instead of raising NoMethodError.
|
|
131
|
+
guess_content_type(new_file.path)
|
|
123
132
|
end
|
|
124
133
|
|
|
125
134
|
original_filename = new_file.try(:original_filename) || new_file.try(:filename) || new_file.try(:path)
|
|
@@ -164,8 +173,12 @@ module Resizing
|
|
|
164
173
|
# CarrierWave::Storage::Fog::File.new(@uploader, @base, new_path)
|
|
165
174
|
# end
|
|
166
175
|
|
|
176
|
+
# NOTE: Resizing::CarrierWave#file calls this on every access, so the cached metadata
|
|
177
|
+
# is only dropped when the identifier actually changes.
|
|
167
178
|
def retrieve(identifier)
|
|
168
|
-
|
|
179
|
+
new_public_id = Resizing::PublicId.new(identifier)
|
|
180
|
+
reset_metadata unless new_public_id.to_s == @public_id.to_s
|
|
181
|
+
@public_id = new_public_id
|
|
169
182
|
end
|
|
170
183
|
|
|
171
184
|
private
|
|
@@ -213,25 +226,60 @@ module Resizing
|
|
|
213
226
|
end
|
|
214
227
|
|
|
215
228
|
##
|
|
216
|
-
#
|
|
229
|
+
# Metadata of the stored image, fetched lazily from the Resizing metadata API
|
|
230
|
+
# and memoized (including a failed lookup, so it is fetched at most once).
|
|
217
231
|
#
|
|
218
|
-
#
|
|
232
|
+
# This replaces the Fog-derived `file` method (`directory.files.head(path)`) that this
|
|
233
|
+
# class used to rely on: Resizing has no equivalent of a remote file handle, and the
|
|
234
|
+
# metadata endpoint is the only source of size / content_type / filename.
|
|
219
235
|
#
|
|
220
|
-
#
|
|
236
|
+
# === Returns
|
|
221
237
|
#
|
|
222
|
-
#
|
|
223
|
-
|
|
224
|
-
|
|
238
|
+
# [Hash, nil] metadata hash, or nil when there is no image or it cannot be fetched
|
|
239
|
+
def metadata
|
|
240
|
+
return @metadata if @metadata_fetched
|
|
241
|
+
|
|
242
|
+
@metadata_fetched = true
|
|
243
|
+
@metadata = fetch_metadata
|
|
244
|
+
end
|
|
225
245
|
|
|
226
|
-
def
|
|
227
|
-
|
|
246
|
+
def reset_metadata
|
|
247
|
+
@metadata = nil
|
|
248
|
+
@metadata_fetched = false
|
|
249
|
+
end
|
|
228
250
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
251
|
+
# NOTE: Never raises. These values are read on ordinary code paths (CarrierWave's
|
|
252
|
+
# Uploader::Proxy delegates size / content_type to this object), so a transient API
|
|
253
|
+
# failure must not break the caller; the absence of metadata is reported as nil.
|
|
254
|
+
def fetch_metadata
|
|
255
|
+
image_id = metadata_image_id
|
|
256
|
+
return nil if image_id.nil? || image_id.empty?
|
|
257
|
+
|
|
258
|
+
result = Resizing.metadata(image_id, {})
|
|
259
|
+
return nil if !result.is_a?(Hash) || result['error']
|
|
260
|
+
|
|
261
|
+
result
|
|
262
|
+
rescue StandardError
|
|
263
|
+
nil
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# The image to look up: the identifier this instance was built with/retrieved,
|
|
267
|
+
# falling back to the value currently stored in the model column.
|
|
268
|
+
def metadata_image_id
|
|
269
|
+
return @public_id.image_id if @public_id.present?
|
|
270
|
+
|
|
271
|
+
Resizing::PublicId.new(model.send(:read_attribute, serialization_column)).image_id
|
|
272
|
+
rescue StandardError
|
|
273
|
+
nil
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Guess the content type from a file name/path.
|
|
277
|
+
# Returns DEFAULT_CONTENT_TYPE when the extension is unknown to MIME::Types,
|
|
278
|
+
# which is the same convention CarrierWave/Rack use for unidentifiable files.
|
|
279
|
+
def guess_content_type(path)
|
|
280
|
+
return DEFAULT_CONTENT_TYPE if path.nil?
|
|
281
|
+
|
|
282
|
+
MIME::Types.type_for(path.to_s).first&.content_type || DEFAULT_CONTENT_TYPE
|
|
235
283
|
end
|
|
236
284
|
|
|
237
285
|
def url_options_supported?(local_file)
|
|
@@ -12,9 +12,13 @@ module Resizing
|
|
|
12
12
|
f
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
# NOTE: CarrierWave 2.2.x never reaches here (Uploader::Remove#remove! calls
|
|
16
|
+
# @file.delete directly), but newer versions may remove through the storage.
|
|
17
|
+
# File#delete takes no argument and resolves the image to delete by itself, so
|
|
18
|
+
# reuse the given file when it is already ours instead of building a new one.
|
|
19
|
+
def remove!(file = nil)
|
|
20
|
+
f = file.is_a?(Resizing::CarrierWave::Storage::File) ? file : Resizing::CarrierWave::Storage::File.new(uploader)
|
|
21
|
+
f.delete
|
|
18
22
|
f
|
|
19
23
|
end
|
|
20
24
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'resizing/carrier_wave/callback_file'
|
|
3
4
|
require 'resizing/carrier_wave/storage/file'
|
|
4
5
|
require 'resizing/carrier_wave/storage/remote'
|
|
5
6
|
|
|
@@ -104,16 +105,50 @@ module Resizing
|
|
|
104
105
|
@transform.push(:resize_to_fit, *args)
|
|
105
106
|
end
|
|
106
107
|
|
|
108
|
+
# Resizing has no cache storage, so the file is uploaded to Resizing directly here
|
|
109
|
+
# instead of being copied to a local workfile and handed to cache_storage.
|
|
110
|
+
#
|
|
111
|
+
# The :cache callbacks are still run, so that the uploader settings hooked on them
|
|
112
|
+
# (extension_allowlist / extension_denylist / content_type_allowlist /
|
|
113
|
+
# content_type_denylist / size_range) are honoured before the upload happens.
|
|
114
|
+
# The callbacks that do not fit the Resizing flow (process! and cache_versions!)
|
|
115
|
+
# are disabled below.
|
|
107
116
|
def cache!(new_file)
|
|
108
117
|
return if new_file.nil?
|
|
109
118
|
|
|
110
|
-
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
119
|
+
# CallbackFile responds to #extension / #content_type / #size, which the
|
|
120
|
+
# before :cache callbacks need, with the extension derived from the content type
|
|
121
|
+
# (see CallbackFile). The original object is handed to the storage, because it
|
|
122
|
+
# carries the original filename sent to Resizing.
|
|
123
|
+
sanitized = CallbackFile.new(new_file)
|
|
124
|
+
return if sanitized.empty?
|
|
125
|
+
|
|
126
|
+
with_callbacks(:cache, sanitized) do
|
|
127
|
+
file = storage.store!(new_file)
|
|
128
|
+
# do not assign @cache_id, bacause resizing do not support cache
|
|
129
|
+
# save to resizing directly
|
|
130
|
+
# @cache_id = file.public_id
|
|
131
|
+
|
|
132
|
+
@filename = file.public_id.to_s
|
|
133
|
+
@file = file
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# process! is called before :cache
|
|
138
|
+
# Disable on Resizing, because `process` is repurposed as a definition of the
|
|
139
|
+
# transformation applied when the browser fetches the image URL (see #transform_string),
|
|
140
|
+
# so the processors must not be run as actual image processing on upload.
|
|
141
|
+
# https://github.com/carrierwaveuploader/carrierwave/blob/28190e99299a6131c0424a5d10205f471e39f3cd/lib/carrierwave/uploader/processing.rb#L12
|
|
142
|
+
def process!(*args)
|
|
143
|
+
# NOP
|
|
144
|
+
end
|
|
114
145
|
|
|
115
|
-
|
|
116
|
-
|
|
146
|
+
# cache_versions! is called after :cache
|
|
147
|
+
# Disable on Resizing, because each version would upload the same image again and
|
|
148
|
+
# the resulting public_id would be stored nowhere, leaving orphan images behind.
|
|
149
|
+
# https://github.com/carrierwaveuploader/carrierwave/blob/28190e99299a6131c0424a5d10205f471e39f3cd/lib/carrierwave/uploader/versions.rb#L17
|
|
150
|
+
def cache_versions!(*args)
|
|
151
|
+
# NOP
|
|
117
152
|
end
|
|
118
153
|
|
|
119
154
|
def filename
|
data/lib/resizing/client.rb
CHANGED
|
@@ -15,10 +15,17 @@ module Resizing
|
|
|
15
15
|
# Resizing::Client.new(configuration)
|
|
16
16
|
#++
|
|
17
17
|
class Configuration
|
|
18
|
-
attr_reader :image_host, :
|
|
18
|
+
attr_reader :image_host, :project_id, :secret_token, :open_timeout, :response_timeout, :enable_mock
|
|
19
19
|
DEFAULT_HOST = 'https://img.resizing.net'
|
|
20
20
|
DEFAULT_IMAGE_HOST = 'https://img.resizing.net'
|
|
21
|
-
|
|
21
|
+
VIDEO_HOST_FALLBACK = 'https://video.resizing.net'
|
|
22
|
+
private_constant :VIDEO_HOST_FALLBACK
|
|
23
|
+
# 動画 API はサービス側で廃止されたため非推奨。将来のバージョンで削除する
|
|
24
|
+
DEFAULT_VIDEO_HOST = VIDEO_HOST_FALLBACK
|
|
25
|
+
deprecate_constant :DEFAULT_VIDEO_HOST
|
|
26
|
+
DEPRECATED_VIDEO_HOST_MESSAGE = '[resizing] video_host is deprecated: ' \
|
|
27
|
+
'the video API has been removed from Resizing and this value is no longer used. ' \
|
|
28
|
+
'It will be deleted in a future version.'
|
|
22
29
|
DEFAULT_OPEN_TIMEOUT = 2
|
|
23
30
|
DEFAULT_RESPONSE_TIMEOUT = 10
|
|
24
31
|
|
|
@@ -28,7 +35,7 @@ module Resizing
|
|
|
28
35
|
case attr = attrs.first
|
|
29
36
|
when Hash
|
|
30
37
|
raise_configiration_error if attr[:project_id].nil? || attr[:secret_token].nil?
|
|
31
|
-
|
|
38
|
+
raise_deprecated_host_error unless blank_value?(attr[:host])
|
|
32
39
|
|
|
33
40
|
initialize_by_hash attr
|
|
34
41
|
return
|
|
@@ -79,32 +86,55 @@ module Resizing
|
|
|
79
86
|
::SecureRandom.uuid
|
|
80
87
|
end
|
|
81
88
|
|
|
89
|
+
# 動画 API の廃止により非推奨。値は保持するが利用されない
|
|
90
|
+
def video_host
|
|
91
|
+
warn_video_host_deprecation
|
|
92
|
+
@video_host
|
|
93
|
+
end
|
|
94
|
+
|
|
82
95
|
def ==(other)
|
|
83
96
|
return false unless self.class == other.class
|
|
84
97
|
|
|
85
|
-
|
|
98
|
+
return false unless @video_host == other.instance_variable_get(:@video_host)
|
|
99
|
+
|
|
100
|
+
%i[image_host project_id secret_token open_timeout response_timeout].all? do |name|
|
|
86
101
|
send(name) == other.send(name)
|
|
87
102
|
end
|
|
88
103
|
end
|
|
89
104
|
|
|
90
105
|
private
|
|
91
106
|
|
|
107
|
+
# ActiveSupport の Object#present? / #blank? に依存しないための素の Ruby 実装。
|
|
108
|
+
# このgemはRails外からも利用されるため、activesupportがロードされていない前提で動作する必要がある。
|
|
109
|
+
def blank_value?(value)
|
|
110
|
+
value.nil? || value.to_s.strip.empty?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def raise_deprecated_host_error
|
|
114
|
+
raise ConfigurationError, 'The host on configuration is deprecated. Use image_host'
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def deprecated_video_host(value)
|
|
118
|
+
warn_video_host_deprecation unless value.nil?
|
|
119
|
+
value.dup.freeze || VIDEO_HOST_FALLBACK
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def warn_video_host_deprecation
|
|
123
|
+
warn DEPRECATED_VIDEO_HOST_MESSAGE
|
|
124
|
+
end
|
|
125
|
+
|
|
92
126
|
def raise_configiration_error
|
|
93
|
-
raise ConfigurationError, 'need hash and some keys like :image_host,
|
|
127
|
+
raise ConfigurationError, 'need hash and some keys like :image_host, :project_id, :secret_token'
|
|
94
128
|
end
|
|
95
129
|
|
|
96
|
-
# rubocop:disable Metrics/AbcSize
|
|
97
130
|
def initialize_by_hash(attr)
|
|
98
|
-
raise 'The host on configuration is deprecated. Use image_host, video_host' if attr[:host].present?
|
|
99
|
-
|
|
100
131
|
@image_host = attr[:image_host].dup.freeze || DEFAULT_IMAGE_HOST
|
|
101
|
-
@video_host = attr[:video_host]
|
|
132
|
+
@video_host = deprecated_video_host(attr[:video_host])
|
|
102
133
|
@project_id = attr[:project_id].dup.freeze
|
|
103
134
|
@secret_token = attr[:secret_token].dup.freeze
|
|
104
135
|
@open_timeout = attr[:open_timeout] || DEFAULT_OPEN_TIMEOUT
|
|
105
136
|
@response_timeout = attr[:response_timeout] || DEFAULT_RESPONSE_TIMEOUT
|
|
106
137
|
@enable_mock = attr[:enable_mock] || false
|
|
107
138
|
end
|
|
108
|
-
# rubocop:enable Metrics/AbcSize
|
|
109
139
|
end
|
|
110
140
|
end
|
data/lib/resizing/mock_client.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
3
5
|
module Resizing
|
|
4
6
|
class MockClient
|
|
5
7
|
def post(_file_or_binary, _options = {})
|
|
@@ -10,33 +12,47 @@ module Resizing
|
|
|
10
12
|
def put(name, _file_or_binary, _options)
|
|
11
13
|
r = load_yaml('test/vcr/client/put.yml')
|
|
12
14
|
result = JSON.parse(r['string'])
|
|
13
|
-
#
|
|
15
|
+
# カセット内の値に依存せず、name と新しい version で id / public_id を組み立て直す
|
|
16
|
+
version = generate_version
|
|
14
17
|
result['id'] = name
|
|
15
|
-
result['
|
|
16
|
-
result['
|
|
18
|
+
result['version'] = version
|
|
19
|
+
result['latest_version_id'] = version
|
|
20
|
+
result['public_id'] = build_public_id(result['public_id'], image_id: name, version: version)
|
|
17
21
|
result
|
|
18
22
|
end
|
|
19
23
|
|
|
20
24
|
def delete(name)
|
|
21
25
|
r = load_yaml('test/vcr/client/delete.yml')
|
|
22
26
|
result = JSON.parse(r['string'])
|
|
23
|
-
#
|
|
27
|
+
# カセット内の値に依存せず、name で id / public_id を組み立て直す
|
|
24
28
|
result['id'] = name
|
|
25
|
-
result['public_id']
|
|
29
|
+
result['public_id'] = build_public_id(result['public_id'], image_id: name)
|
|
26
30
|
result
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
def metadata(name, _options = {})
|
|
30
34
|
r = load_yaml('test/vcr/client/metadata.yml')
|
|
31
35
|
result = JSON.parse(r['string'])
|
|
32
|
-
#
|
|
36
|
+
# カセット内の値に依存せず、name で id / public_id を組み立て直す
|
|
33
37
|
result['id'] = name
|
|
34
|
-
result['public_id']
|
|
38
|
+
result['public_id'] = build_public_id(result['public_id'], image_id: name)
|
|
35
39
|
result
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
private
|
|
39
43
|
|
|
44
|
+
# カセットの public_id を PublicId で分解し、image_id / version を差し替えて組み立て直す
|
|
45
|
+
def build_public_id(public_id, image_id:, version: nil)
|
|
46
|
+
parsed = Resizing::PublicId.new(public_id)
|
|
47
|
+
version ||= parsed.version
|
|
48
|
+
identifier = "/projects/#{parsed.project_id}/upload/images/#{image_id}"
|
|
49
|
+
version.nil? ? identifier : "#{identifier}/v#{version}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def generate_version
|
|
53
|
+
SecureRandom.alphanumeric(32)
|
|
54
|
+
end
|
|
55
|
+
|
|
40
56
|
def load_yaml(filename)
|
|
41
57
|
path = "#{library_root}/#{filename}"
|
|
42
58
|
YAML.load_file(path)['http_interactions'].first['response']['body']
|
data/lib/resizing/public_id.rb
CHANGED
|
@@ -7,8 +7,9 @@ module Resizing
|
|
|
7
7
|
parsed
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
# nil / 空文字 / 空白のみの文字列はいずれも「値なし」として扱う
|
|
10
11
|
def empty?
|
|
11
|
-
@public_id.to_s.empty?
|
|
12
|
+
@public_id.to_s.strip.empty?
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def image_id
|
|
@@ -39,7 +40,7 @@ module Resizing
|
|
|
39
40
|
private
|
|
40
41
|
|
|
41
42
|
def parsed
|
|
42
|
-
return nil if
|
|
43
|
+
return nil if empty?
|
|
43
44
|
|
|
44
45
|
unless defined? @parsed
|
|
45
46
|
@parsed = Resizing.separate_public_id(@public_id)
|
data/lib/resizing/version.rb
CHANGED
data/lib/resizing.rb
CHANGED
|
@@ -14,7 +14,6 @@ module Resizing
|
|
|
14
14
|
autoload :Configuration, 'resizing/configuration'
|
|
15
15
|
autoload :CarrierWave, 'resizing/carrier_wave'
|
|
16
16
|
autoload :PublicId, 'resizing/public_id'
|
|
17
|
-
autoload :Video, 'resizing/video'
|
|
18
17
|
autoload :ActiveStorage, 'resizing/active_storage'
|
|
19
18
|
|
|
20
19
|
class Error < StandardError; end
|