resizing 1.2.1 → 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/.gitignore +6 -0
- 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 +120 -47
- data/lib/resizing/carrier_wave/storage/remote.rb +7 -3
- data/lib/resizing/carrier_wave.rb +51 -7
- 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 +477 -23
- 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 +358 -11
- data/test/vcr/carrier_wave_test/update_image.yml +63 -0
- metadata +35 -10
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,29 +7,40 @@ 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
|
-
# rubocop:disable Metrics/AbcSize
|
|
43
|
+
# rubocop:disable Metrics/AbcSize
|
|
33
44
|
def delete
|
|
34
45
|
# Use the identifier from constructor if available, otherwise try to get from model
|
|
35
46
|
if @public_id.present?
|
|
@@ -44,23 +55,27 @@ module Resizing
|
|
|
44
55
|
@public_id = Resizing::PublicId.new(column_value)
|
|
45
56
|
end
|
|
46
57
|
|
|
47
|
-
return if @public_id.empty?
|
|
58
|
+
return if @public_id.empty?
|
|
48
59
|
|
|
49
|
-
# 以下、既存のコード(変更なし)
|
|
50
60
|
resp = client.delete(@public_id.image_id)
|
|
61
|
+
|
|
62
|
+
# NOTE: 削除時のカラムクリアは以下の理由で必要:
|
|
63
|
+
# - 画像更新時: 古い画像IDと新しい画像IDが異なるため、古い画像削除時に新しいIDを消さないようにする
|
|
64
|
+
# - 明示的なremove!時: カラムをnilにする必要がある
|
|
65
|
+
# - clear_column_if_current_imageは削除される画像IDと現在のカラム値を比較して判断
|
|
51
66
|
if resp['error'] == 'ActiveRecord::RecordNotFound' # 404 not found
|
|
52
|
-
|
|
67
|
+
clear_column_if_current_image
|
|
53
68
|
return
|
|
54
69
|
end
|
|
55
70
|
|
|
56
71
|
if @public_id.image_id == resp['id']
|
|
57
|
-
|
|
72
|
+
clear_column_if_current_image
|
|
58
73
|
return
|
|
59
74
|
end
|
|
60
75
|
|
|
61
76
|
raise APIError, "raise someone error:#{resp.inspect}"
|
|
62
77
|
end
|
|
63
|
-
# rubocop:enable Metrics/AbcSize
|
|
78
|
+
# rubocop:enable Metrics/AbcSize
|
|
64
79
|
|
|
65
80
|
def extension
|
|
66
81
|
raise NotImplementedError, 'this method is do not used. maybe'
|
|
@@ -69,32 +84,32 @@ module Resizing
|
|
|
69
84
|
##
|
|
70
85
|
# Read content of file from service
|
|
71
86
|
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
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.
|
|
75
92
|
def read
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return if file_body.nil?
|
|
79
|
-
return file_body unless file_body.is_a?(::File)
|
|
80
|
-
|
|
81
|
-
# Fog::Storage::XXX::File#body could return the source file which was upoloaded to the remote server.
|
|
82
|
-
read_source_file(file_body) if ::File.exist?(file_body.path)
|
|
83
|
-
|
|
84
|
-
# If the source file doesn't exist, the remote content is read
|
|
85
|
-
@file = nil
|
|
86
|
-
file.body
|
|
93
|
+
raise NotImplementedError, 'Resizing does not support reading the stored image content'
|
|
87
94
|
end
|
|
88
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.
|
|
89
100
|
def size
|
|
90
|
-
|
|
101
|
+
(metadata && metadata['size']) || 0
|
|
91
102
|
end
|
|
92
103
|
|
|
93
104
|
def exists?
|
|
94
|
-
|
|
105
|
+
!metadata.nil?
|
|
95
106
|
end
|
|
96
107
|
|
|
97
108
|
def current_path
|
|
109
|
+
# Return the path from @public_id if set (for retrieve scenarios),
|
|
110
|
+
# otherwise fall back to reading from model
|
|
111
|
+
return @public_id.to_s if @public_id.present?
|
|
112
|
+
|
|
98
113
|
@current_path = model.send :read_attribute, serialization_column
|
|
99
114
|
end
|
|
100
115
|
alias path current_path
|
|
@@ -110,8 +125,10 @@ module Resizing
|
|
|
110
125
|
@content_type ||= if new_file.respond_to? :content_type
|
|
111
126
|
new_file.content_type
|
|
112
127
|
else
|
|
113
|
-
#
|
|
114
|
-
|
|
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)
|
|
115
132
|
end
|
|
116
133
|
|
|
117
134
|
original_filename = new_file.try(:original_filename) || new_file.try(:filename) || new_file.try(:path)
|
|
@@ -131,11 +148,14 @@ module Resizing
|
|
|
131
148
|
@public_id = Resizing::PublicId.new(@response['public_id'])
|
|
132
149
|
@content_type = @response['content_type']
|
|
133
150
|
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
|
|
151
|
+
# NOTE: 理想的にはStorage::File内でモデルのカラムをいじらず、CarrierWaveに任せるべきだが、
|
|
152
|
+
# 現在の実装では以下の理由で必要:
|
|
153
|
+
# - CarrierWaveは write_uploader(column, mounter.identifiers.first) でカラムを更新
|
|
154
|
+
# - mounter.identifiers -> uploaders.map(&:identifier) -> storage.identifier -> uploader.filename
|
|
155
|
+
# - resizing-gemの filenameメソッドは read_column を返す(既存のカラム値)
|
|
156
|
+
# - そのため、CarrierWaveに任せると旧い値が書き戻されてしまう
|
|
157
|
+
# TODO: これを修正するには、Remote#identifierをオーバーライドして@public_id.to_sを返すか、
|
|
158
|
+
# uploader.filenameの実装を変更する必要がある
|
|
139
159
|
# save new value to model class
|
|
140
160
|
model.send :write_attribute, serialization_column, @public_id.to_s
|
|
141
161
|
|
|
@@ -153,8 +173,12 @@ module Resizing
|
|
|
153
173
|
# CarrierWave::Storage::Fog::File.new(@uploader, @base, new_path)
|
|
154
174
|
# end
|
|
155
175
|
|
|
176
|
+
# NOTE: Resizing::CarrierWave#file calls this on every access, so the cached metadata
|
|
177
|
+
# is only dropped when the identifier actually changes.
|
|
156
178
|
def retrieve(identifier)
|
|
157
|
-
|
|
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
|
|
158
182
|
end
|
|
159
183
|
|
|
160
184
|
private
|
|
@@ -177,6 +201,20 @@ module Resizing
|
|
|
177
201
|
@serialization_column ||= model.send(:_mounter, uploader.mounted_as).send(:serialization_column)
|
|
178
202
|
end
|
|
179
203
|
|
|
204
|
+
# Only clear the column if the deleted image is the current one
|
|
205
|
+
# (not when deleting an old image during update)
|
|
206
|
+
def clear_column_if_current_image
|
|
207
|
+
return if model.destroyed?
|
|
208
|
+
|
|
209
|
+
current_value = model.send(:read_attribute, serialization_column)
|
|
210
|
+
current_public_id = Resizing::PublicId.new(current_value)
|
|
211
|
+
|
|
212
|
+
# Only clear if the deleted image is the same as the current one
|
|
213
|
+
return unless current_public_id.image_id == @public_id.image_id
|
|
214
|
+
|
|
215
|
+
model.send :write_attribute, serialization_column, nil
|
|
216
|
+
end
|
|
217
|
+
|
|
180
218
|
##
|
|
181
219
|
# client of Resizing
|
|
182
220
|
def client
|
|
@@ -188,25 +226,60 @@ module Resizing
|
|
|
188
226
|
end
|
|
189
227
|
|
|
190
228
|
##
|
|
191
|
-
#
|
|
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).
|
|
192
231
|
#
|
|
193
|
-
#
|
|
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.
|
|
194
235
|
#
|
|
195
|
-
#
|
|
236
|
+
# === Returns
|
|
196
237
|
#
|
|
197
|
-
#
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
200
241
|
|
|
201
|
-
|
|
202
|
-
|
|
242
|
+
@metadata_fetched = true
|
|
243
|
+
@metadata = fetch_metadata
|
|
244
|
+
end
|
|
203
245
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
246
|
+
def reset_metadata
|
|
247
|
+
@metadata = nil
|
|
248
|
+
@metadata_fetched = false
|
|
249
|
+
end
|
|
250
|
+
|
|
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
|
|
210
283
|
end
|
|
211
284
|
|
|
212
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
|
|
|
@@ -29,11 +30,20 @@ module Resizing
|
|
|
29
30
|
def initialize(*args)
|
|
30
31
|
@requested_format = nil
|
|
31
32
|
@default_format = nil
|
|
33
|
+
@retrieved_identifier = nil
|
|
32
34
|
super
|
|
33
35
|
end
|
|
34
36
|
|
|
37
|
+
# Override to store the identifier and set up @file for later use
|
|
38
|
+
def retrieve_from_store!(identifier)
|
|
39
|
+
@retrieved_identifier = identifier
|
|
40
|
+
super
|
|
41
|
+
# Ensure @file is set up so that remove! can call @file.delete
|
|
42
|
+
file
|
|
43
|
+
end
|
|
44
|
+
|
|
35
45
|
def file
|
|
36
|
-
file_identifier = identifier || read_column
|
|
46
|
+
file_identifier = @retrieved_identifier || identifier || read_column
|
|
37
47
|
|
|
38
48
|
return nil if file_identifier.blank?
|
|
39
49
|
|
|
@@ -95,16 +105,50 @@ module Resizing
|
|
|
95
105
|
@transform.push(:resize_to_fit, *args)
|
|
96
106
|
end
|
|
97
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.
|
|
98
116
|
def cache!(new_file)
|
|
99
117
|
return if new_file.nil?
|
|
100
118
|
|
|
101
|
-
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
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
|
|
105
136
|
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
145
|
+
|
|
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
|
|
108
152
|
end
|
|
109
153
|
|
|
110
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
|