cloudinary 1.29.0 → 2.3.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/.travis.yml +0 -1
- data/CHANGELOG.md +94 -0
- data/README.md +5 -3
- data/cloudinary.gemspec +25 -50
- data/lib/active_storage/service/cloudinary_service.rb +61 -8
- data/lib/cloudinary/account_api.rb +39 -8
- data/lib/cloudinary/analytics.rb +157 -0
- data/lib/cloudinary/api.rb +68 -10
- data/lib/cloudinary/auth_token.rb +1 -5
- data/lib/cloudinary/base_api.rb +36 -31
- data/lib/cloudinary/carrier_wave/storage.rb +2 -1
- data/lib/cloudinary/carrier_wave.rb +0 -5
- data/lib/cloudinary/helper.rb +3 -11
- data/lib/cloudinary/migrator.rb +70 -71
- data/lib/cloudinary/uploader.rb +70 -90
- data/lib/cloudinary/utils.rb +32 -55
- data/lib/cloudinary/version.rb +1 -1
- data/lib/cloudinary.rb +3 -9
- data/lib/tasks/cloudinary/fetch_assets.rake +9 -3
- data/vendor/assets/javascripts/cloudinary/jquery.cloudinary.js +3 -3
- metadata +179 -38
- data/lib/cloudinary/ostruct2.rb +0 -284
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1244ab3ce91f6fda0b07e5438ed79f896cde743306efb9c3ac58e5a5f1d4a6dc
|
4
|
+
data.tar.gz: a36510b3d2a2e1b788c2455e5def6f314b78b0b94def573a75a4750e96498b6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ba876181927174ec445d3abd04c79cc03af3837713c234ea9a89651131b6dd4c4be6e3ec02d155596d4f2ce821b269600adda74210408af066b04e0671169fb
|
7
|
+
data.tar.gz: 8bcbb47be56d985537d237fcd5989979555ba0d622bf8a6a3f60484e8b31ffd7d0682353ce4768e4f732edb3fe1a909e001640226fc8fe085ff35cc321b662c6
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,97 @@
|
|
1
|
+
2.3.0 / 2025-02-20
|
2
|
+
==================
|
3
|
+
|
4
|
+
New functionality and features
|
5
|
+
------------------------------
|
6
|
+
|
7
|
+
* Add support for `429 Too Many Requests` HTTP status code
|
8
|
+
* Add support for `allow_dynamic_list_values` parameter in `MetadataField`
|
9
|
+
* Add support for `config` Admin API
|
10
|
+
|
11
|
+
2.2.0 / 2024-09-08
|
12
|
+
==================
|
13
|
+
|
14
|
+
New functionality and features
|
15
|
+
------------------------------
|
16
|
+
|
17
|
+
* Add support for ActiveStorage model service configuration
|
18
|
+
|
19
|
+
Other Changes
|
20
|
+
-------------
|
21
|
+
|
22
|
+
* Fix asset type detection in ActiveStorage
|
23
|
+
* Add explicit `ostruct` dependency
|
24
|
+
|
25
|
+
2.1.2 / 2024-08-18
|
26
|
+
==================
|
27
|
+
|
28
|
+
* Fix ActiveStorage type detection for email files
|
29
|
+
* Add `changelog_uri` to `gemspec`
|
30
|
+
|
31
|
+
2.1.1 / 2024-05-28
|
32
|
+
==================
|
33
|
+
|
34
|
+
* Fix regression in `cloudinary_url` generation
|
35
|
+
|
36
|
+
2.1.0 / 2024-05-27
|
37
|
+
==================
|
38
|
+
|
39
|
+
New functionality and features
|
40
|
+
------------------------------
|
41
|
+
|
42
|
+
* Add support for `rename_folder` Admin API
|
43
|
+
* Add support for `delete_access_key` in Provisioning API
|
44
|
+
|
45
|
+
Other Changes
|
46
|
+
-------------
|
47
|
+
|
48
|
+
* Fix detection of base64 encoded files with complex MIME type
|
49
|
+
* Fix `fetch` url generation with Active Storage `folder` configuration
|
50
|
+
* Fix usage of deprecated `File.exists?` method
|
51
|
+
|
52
|
+
2.0.2 / 2024-04-09
|
53
|
+
==================
|
54
|
+
|
55
|
+
* Fix CarrierWave `store!` method
|
56
|
+
|
57
|
+
2.0.1 / 2024-04-09
|
58
|
+
==================
|
59
|
+
|
60
|
+
* Fix rake tasks
|
61
|
+
|
62
|
+
2.0.0 / 2024-04-08
|
63
|
+
==================
|
64
|
+
|
65
|
+
Breaking Changes
|
66
|
+
----------------
|
67
|
+
|
68
|
+
* Set minimal Ruby version to `3.0`
|
69
|
+
* For older Rubies use version `1.x` of this library
|
70
|
+
* Replace `rest-client` with `faraday`
|
71
|
+
* Set config `secure` to `true` by default
|
72
|
+
* All delivery URLs are set to be `https://` by default
|
73
|
+
* To disable - set `secure` to `false` in config/options
|
74
|
+
* Add support for URL Analytics
|
75
|
+
* Adds analytics signature query parameter to the delivery URLs
|
76
|
+
* To disable - set `analytics` to `false` in config/options
|
77
|
+
* Remove deprecated methods
|
78
|
+
* `Cloudinary::Utils.unsigned_download_url`
|
79
|
+
* use `Cloudinary::Utils.cloudinary_url` instead
|
80
|
+
* `Cloudinary::Utils.signed_download_url`
|
81
|
+
* use `Cloudinary::Utils.cloudinary_url` instead
|
82
|
+
* `Cloudinary::Utils.zip_download_url`
|
83
|
+
* use `Cloudinary::Utils.download_zip_url` instead
|
84
|
+
* `cl_zip_download_url`
|
85
|
+
* use `cl_download_zip_url` instead
|
86
|
+
* Remove deprecated constants
|
87
|
+
|
88
|
+
New functionality and features
|
89
|
+
------------------------------
|
90
|
+
|
91
|
+
* Add support for `analyze` API
|
92
|
+
* Support chunked uploads with CarrierWave
|
93
|
+
* Filter users by last login in `users` Provisioning API
|
94
|
+
|
1
95
|
1.29.0 / 2024-02-26
|
2
96
|
==================
|
3
97
|
|
data/README.md
CHANGED
@@ -40,11 +40,13 @@ the [Ruby on Rails SDK Guide](https://cloudinary.com/documentation/rails_integra
|
|
40
40
|
|
41
41
|
| SDK Version | Ruby 1.9.3 | Ruby 2.x | Ruby 3.x |
|
42
42
|
|-------------|------------|----------|----------|
|
43
|
-
| 1.x |
|
43
|
+
| 1.x | ✔ | ✔ | ✔ |
|
44
|
+
| 2.x | ✘ | ✘ | ✔ |
|
44
45
|
|
45
46
|
| SDK Version | Rails 5.x | Rails 6.x | Rails 7.x |
|
46
47
|
|-------------|-----------|-----------|-----------|
|
47
|
-
| 1.x |
|
48
|
+
| 1.x | ✔ | ✔ | ✔ |
|
49
|
+
| 2.x | ✘ | ✔ | ✔ |
|
48
50
|
|
49
51
|
## Installation
|
50
52
|
|
@@ -64,7 +66,7 @@ require 'cloudinary'
|
|
64
66
|
- [See full documentation](https://cloudinary.com/documentation/rails_image_manipulation).
|
65
67
|
|
66
68
|
```ruby
|
67
|
-
cl_image_tag("sample.jpg", :
|
69
|
+
cl_image_tag("sample.jpg", width: 100, height: 150, crop: "fill")
|
68
70
|
```
|
69
71
|
|
70
72
|
### Upload
|
data/cloudinary.gemspec
CHANGED
@@ -7,63 +7,38 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = Cloudinary::VERSION
|
8
8
|
s.authors = ["Nadav Soferman","Itai Lahan","Tal Lev-Ami"]
|
9
9
|
s.email = ["nadav.soferman@cloudinary.com","itai.lahan@cloudinary.com","tal.levami@cloudinary.com"]
|
10
|
-
s.homepage = "
|
10
|
+
s.homepage = "https://cloudinary.com"
|
11
11
|
s.license = "MIT"
|
12
12
|
|
13
13
|
s.summary = %q{Client library for easily using the Cloudinary service}
|
14
14
|
s.description = %q{Client library for easily using the Cloudinary service}
|
15
15
|
|
16
|
-
s.
|
16
|
+
s.metadata = {
|
17
|
+
"changelog_uri" => "https://github.com/cloudinary/cloudinary_gem/blob/master/CHANGELOG.md"
|
18
|
+
}
|
17
19
|
|
18
|
-
s.files = `git ls-files`.split("\n").select { |f| !f.start_with?("test", "spec", "features", "samples") } +
|
20
|
+
s.files = `git ls-files`.split("\n").select { |f| !f.start_with?("test", "spec", "features", "samples") } +
|
21
|
+
Dir.glob("vendor/assets/javascripts/*/*") + Dir.glob("vendor/assets/html/*")
|
19
22
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
23
|
s.require_paths = ["lib"]
|
21
24
|
|
22
|
-
s.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
s.add_development_dependency "
|
35
|
-
s.add_development_dependency "
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
if RUBY_VERSION >= "3.0.0"
|
43
|
-
s.add_development_dependency "sqlite3"
|
44
|
-
else
|
45
|
-
s.add_development_dependency "sqlite3", "< 1.6.0"
|
46
|
-
end
|
47
|
-
|
48
|
-
s.add_development_dependency "rspec", '>=3.5'
|
49
|
-
s.add_development_dependency "rspec-retry"
|
50
|
-
|
51
|
-
if RUBY_VERSION >= "3.0.0"
|
52
|
-
s.add_development_dependency "rails", "~> 6.0.3"
|
53
|
-
elsif RUBY_VERSION >= "2.2.2"
|
54
|
-
s.add_development_dependency "rails", "~> 5.2"
|
55
|
-
end
|
56
|
-
|
57
|
-
s.add_development_dependency "railties", "<= 4.2.7" if RUBY_VERSION <= "1.9.3"
|
58
|
-
s.add_development_dependency "rspec-rails"
|
59
|
-
|
60
|
-
s.add_development_dependency "rubyzip"
|
61
|
-
|
62
|
-
if RUBY_VERSION <= "2.4.0"
|
63
|
-
s.add_development_dependency "simplecov", "<= 0.17.1" # support testing Ruby 1.9
|
64
|
-
s.add_development_dependency 'loofah', '~>2.19.1'
|
65
|
-
s.add_development_dependency "rails-html-sanitizer", "<1.5.0"
|
66
|
-
else
|
67
|
-
s.add_development_dependency "simplecov", "> 0.18.0"
|
68
|
-
end
|
25
|
+
s.required_ruby_version = '~> 3'
|
26
|
+
|
27
|
+
s.add_dependency "faraday", ">= 2.0.1", "< 3.0.0"
|
28
|
+
s.add_dependency "faraday-multipart", "~> 1.0", ">= 1.0.4"
|
29
|
+
s.add_dependency 'faraday-follow_redirects', '~> 0.3.0'
|
30
|
+
s.add_dependency "ostruct"
|
31
|
+
|
32
|
+
s.add_development_dependency "rails", ">= 6.1.7", "< 8.0.0"
|
33
|
+
s.add_development_dependency "rexml", ">= 3.2.5", "< 4.0.0"
|
34
|
+
s.add_development_dependency "actionpack", ">= 6.1.7", "< 8.0.0"
|
35
|
+
s.add_development_dependency "nokogiri", ">= 1.12.5", "< 2.0.0"
|
36
|
+
s.add_development_dependency "rake", ">= 13.0.6", "< 14.0.0"
|
37
|
+
s.add_development_dependency "sqlite3", ">= 1.4.2", "< 2.0.0"
|
38
|
+
s.add_development_dependency "rspec", ">= 3.11.2", "< 4.0.0"
|
39
|
+
s.add_development_dependency "rspec-retry", ">= 0.6.2", "< 1.0.0"
|
40
|
+
s.add_development_dependency "railties", ">= 6.0.4", "< 8.0.0"
|
41
|
+
s.add_development_dependency "rspec-rails", ">= 6.0.4", "< 7.0.0"
|
42
|
+
s.add_development_dependency "rubyzip", ">= 2.3.0", "< 3.0.0"
|
43
|
+
s.add_development_dependency "simplecov", ">= 0.21.2", "< 1.0.0"
|
69
44
|
end
|
@@ -17,7 +17,13 @@ module CloudinaryHelper
|
|
17
17
|
alias cloudinary_url_internal_original cloudinary_url_internal
|
18
18
|
|
19
19
|
def cloudinary_url_internal(source, options = {})
|
20
|
-
|
20
|
+
service_instance, options = ActiveStorage::Service::CloudinaryService.fetch_service_instance_and_config(source, options)
|
21
|
+
service_instance ||= ActiveStorage::Blob.service
|
22
|
+
|
23
|
+
if defined?(service_instance.public_id) && options.fetch(:type, "").to_s != "fetch"
|
24
|
+
source = service_instance.public_id(source)
|
25
|
+
end
|
26
|
+
|
21
27
|
cloudinary_url_internal_original(source, options)
|
22
28
|
end
|
23
29
|
end
|
@@ -39,10 +45,12 @@ module ActiveStorage
|
|
39
45
|
begin
|
40
46
|
extra_headers = checksum.nil? ? {} : {Headers::CONTENT_MD5 => checksum}
|
41
47
|
options = @options.merge(options)
|
48
|
+
resource_type = resource_type(io, key)
|
49
|
+
options[:format] = ext_for_file(key) if resource_type == "raw"
|
42
50
|
Cloudinary::Uploader.upload_large(
|
43
51
|
io,
|
44
52
|
public_id: public_id_internal(key),
|
45
|
-
resource_type: resource_type
|
53
|
+
resource_type: resource_type,
|
46
54
|
context: {active_storage_key: key, checksum: checksum},
|
47
55
|
extra_headers: extra_headers,
|
48
56
|
**options
|
@@ -54,9 +62,10 @@ module ActiveStorage
|
|
54
62
|
end
|
55
63
|
|
56
64
|
def url(key, filename: nil, content_type: '', **options)
|
65
|
+
key = find_blob_or_use_key(key)
|
57
66
|
instrument :url, key: key do |payload|
|
58
67
|
url = Cloudinary::Utils.cloudinary_url(
|
59
|
-
full_public_id_internal(key),
|
68
|
+
full_public_id_internal(key, options),
|
60
69
|
resource_type: resource_type(nil, key, content_type),
|
61
70
|
format: ext_for_file(key, filename, content_type),
|
62
71
|
**@options.merge(options.symbolize_keys)
|
@@ -97,6 +106,7 @@ module ActiveStorage
|
|
97
106
|
end
|
98
107
|
|
99
108
|
def delete(key)
|
109
|
+
key = find_blob_or_use_key(key)
|
100
110
|
instrument :delete, key: key do
|
101
111
|
options = {
|
102
112
|
resource_type: resource_type(nil, key),
|
@@ -113,6 +123,7 @@ module ActiveStorage
|
|
113
123
|
end
|
114
124
|
|
115
125
|
def exist?(key)
|
126
|
+
key = find_blob_or_use_key(key)
|
116
127
|
instrument :exist, key: key do |payload|
|
117
128
|
begin
|
118
129
|
options = {
|
@@ -149,8 +160,7 @@ module ActiveStorage
|
|
149
160
|
|
150
161
|
# Return the partial content in the byte +range+ of the file at the +key+.
|
151
162
|
def download_chunk(key, range)
|
152
|
-
|
153
|
-
uri = URI(url)
|
163
|
+
uri = URI(url(key))
|
154
164
|
instrument :download, key: key do
|
155
165
|
req = Net::HTTP::Get.new(uri)
|
156
166
|
range_end = case
|
@@ -186,6 +196,33 @@ module ActiveStorage
|
|
186
196
|
full_public_id_internal(public_id)
|
187
197
|
end
|
188
198
|
|
199
|
+
def self.fetch_service_instance_and_config(source, options)
|
200
|
+
return [nil, options] unless defined?(ActiveStorage::BlobKey) && source.is_a?(ActiveStorage::BlobKey) &&
|
201
|
+
source.respond_to?(:attributes) && source.attributes.key?(:service_name)
|
202
|
+
|
203
|
+
service_name = source.attributes[:service_name]
|
204
|
+
|
205
|
+
begin
|
206
|
+
service_instance = ActiveStorage::Blob.services.fetch(service_name.to_sym)
|
207
|
+
|
208
|
+
unless service_instance.instance_of?(ActiveStorage::Service::CloudinaryService)
|
209
|
+
Rails.logger.error "Expected service instance #{service_instance.class.name} to be of type ActiveStorage::Service::CloudinaryService."
|
210
|
+
return [nil, options]
|
211
|
+
end
|
212
|
+
|
213
|
+
service_config = Rails.application.config.active_storage.service_configurations.fetch(service_name)
|
214
|
+
options = service_config.merge(options)
|
215
|
+
rescue NameError => e
|
216
|
+
Rails.logger.error "Failed to retrieve the service instance for #{service_name}: #{e.message}"
|
217
|
+
return [nil, options]
|
218
|
+
rescue => e
|
219
|
+
Rails.logger.error "An unexpected error occurred: #{e.message}"
|
220
|
+
return [nil, options]
|
221
|
+
end
|
222
|
+
|
223
|
+
[service_instance, options]
|
224
|
+
end
|
225
|
+
|
189
226
|
private
|
190
227
|
|
191
228
|
def api_uri(action, options)
|
@@ -229,10 +266,12 @@ module ActiveStorage
|
|
229
266
|
end
|
230
267
|
|
231
268
|
# Returns the full public id including folder.
|
232
|
-
def full_public_id_internal(key)
|
269
|
+
def full_public_id_internal(key, options = {})
|
233
270
|
public_id = public_id_internal(key)
|
234
271
|
|
235
|
-
|
272
|
+
options = @options.merge(options)
|
273
|
+
|
274
|
+
return public_id if !options[:folder] || options.fetch(:type, "").to_s == "fetch"
|
236
275
|
|
237
276
|
File.join(@options.fetch(:folder), public_id)
|
238
277
|
end
|
@@ -249,7 +288,7 @@ module ActiveStorage
|
|
249
288
|
case type
|
250
289
|
when 'video', 'audio'
|
251
290
|
'video'
|
252
|
-
when 'text'
|
291
|
+
when 'text', 'message'
|
253
292
|
'raw'
|
254
293
|
when 'application'
|
255
294
|
case subtype
|
@@ -272,5 +311,19 @@ module ActiveStorage
|
|
272
311
|
end
|
273
312
|
content_type_to_resource_type(content_type)
|
274
313
|
end
|
314
|
+
|
315
|
+
def find_blob_or_use_key(key)
|
316
|
+
if key.is_a?(ActiveStorage::BlobKey)
|
317
|
+
key
|
318
|
+
else
|
319
|
+
begin
|
320
|
+
blob = ActiveStorage::Blob.find_by(key: key)
|
321
|
+
blob ? ActiveStorage::BlobKey.new(blob.attributes.as_json) : key
|
322
|
+
rescue ActiveRecord::StatementInvalid => e
|
323
|
+
# Return the original key if an error occurs
|
324
|
+
key
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
275
328
|
end
|
276
329
|
end
|
@@ -127,18 +127,30 @@ class Cloudinary::AccountApi
|
|
127
127
|
call_account_api(:get, ['users', user_id], {}, options.merge(content_type: :json))
|
128
128
|
end
|
129
129
|
|
130
|
-
#
|
131
|
-
#
|
132
|
-
# @param [
|
133
|
-
# @param [String]
|
134
|
-
# @param [String]
|
135
|
-
# @param [
|
130
|
+
# Get a list of the users according to filters.
|
131
|
+
#
|
132
|
+
# @param [Boolean] pending Optional. Limit results to pending users (true), users that are not pending (false), or all users (null)
|
133
|
+
# @param [Array<String>] user_ids Optional. List of user IDs. Up to 100
|
134
|
+
# @param [String] prefix Optional. Search by prefix of the user's name or email. Case-insensitive
|
135
|
+
# @param [String] sub_account_id Optional. Return only users who have access to the given sub-account
|
136
|
+
# @param [Object] options Generic advanced options map, see online documentation.
|
137
|
+
# @option options [Boolean] :last_login Optional. Return only users that last logged in in the specified range of dates (true),
|
138
|
+
# users that didn't last logged in in that range (false), or all users (null).
|
139
|
+
# @option options [Date] :from Optional. Last login start date.
|
140
|
+
# @option options [Date] :to Optional. Last login end date.
|
141
|
+
#
|
142
|
+
# @return [Cloudinary::Api::Response] the users' details.
|
143
|
+
#
|
144
|
+
# @raise [Cloudinary::Api::Error] If the request fails.
|
136
145
|
def self.users(pending = nil, user_ids = [], prefix = nil, sub_account_id = nil, options = {})
|
137
146
|
params = {
|
138
147
|
ids: user_ids,
|
139
148
|
prefix: prefix,
|
140
149
|
sub_account_id: sub_account_id,
|
141
|
-
pending: pending
|
150
|
+
pending: pending,
|
151
|
+
last_login: options[:last_login].to_s,
|
152
|
+
from: Cloudinary::Utils.to_usage_api_date_format(options[:from]),
|
153
|
+
to: Cloudinary::Utils.to_usage_api_date_format(options[:to])
|
142
154
|
}
|
143
155
|
|
144
156
|
call_account_api(:get, 'users', params, options.merge(content_type: :json))
|
@@ -248,16 +260,35 @@ class Cloudinary::AccountApi
|
|
248
260
|
call_account_api(:put, ['sub_accounts', sub_account_id, 'access_keys', api_key], params, options.merge(content_type: :json))
|
249
261
|
end
|
250
262
|
|
263
|
+
# Deletes access key.
|
264
|
+
#
|
265
|
+
# @param [String] sub_account_id The ID of the sub-account.
|
266
|
+
# @param [String, nil] api_key The API key.
|
267
|
+
# @param [String, nil] name The display name as shown in the management console.
|
268
|
+
# @param [Object] options Additional options.
|
269
|
+
def self.delete_access_key(sub_account_id, api_key = nil, name = nil, options = {})
|
270
|
+
uri = ['sub_accounts', sub_account_id, 'access_keys']
|
271
|
+
unless api_key.blank?
|
272
|
+
uri.append(api_key)
|
273
|
+
end
|
274
|
+
|
275
|
+
params = {
|
276
|
+
name: name,
|
277
|
+
}
|
278
|
+
call_account_api(:delete, uri, params, options.merge(content_type: :json))
|
279
|
+
end
|
280
|
+
|
251
281
|
def self.call_account_api(method, uri, params, options)
|
252
282
|
account_id = options[:account_id] || Cloudinary.account_config.account_id || raise('Must supply account_id')
|
253
283
|
api_key = options[:provisioning_api_key] || Cloudinary.account_config.provisioning_api_key || raise('Must supply provisioning api_key')
|
254
284
|
api_secret = options[:provisioning_api_secret] || Cloudinary.account_config.provisioning_api_secret || raise('Must supply provisioning api_secret')
|
285
|
+
api_version = options[:api_version] || Cloudinary.config.api_version || 'v1_1'
|
255
286
|
|
256
287
|
params.reject! { |_, v| v.nil? }
|
257
288
|
auth = { :key => api_key, :secret => api_secret }
|
258
289
|
|
259
290
|
call_cloudinary_api(method, uri, auth, params, options) do |cloudinary, inner_uri|
|
260
|
-
[cloudinary,
|
291
|
+
[cloudinary, api_version, 'provisioning', 'accounts', account_id, inner_uri]
|
261
292
|
end
|
262
293
|
end
|
263
294
|
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cloudinary
|
4
|
+
module Analytics
|
5
|
+
extend self
|
6
|
+
|
7
|
+
QUERY_KEY = '_a'
|
8
|
+
ALGO_VERSION = 'B' # The version of the algorithm
|
9
|
+
SDK_CODE = 'C' # Cloudinary Ruby SDK
|
10
|
+
|
11
|
+
@product = 'A' # Official SDK. Set to 'B' for integrations.
|
12
|
+
@sdk_code = SDK_CODE
|
13
|
+
@sdk_version = Cloudinary::VERSION
|
14
|
+
@tech_version = "#{RUBY_VERSION[/\d+\.\d+/]}"
|
15
|
+
|
16
|
+
CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
17
|
+
BINARY_PAD_SIZE = 6
|
18
|
+
|
19
|
+
@char_codes = nil
|
20
|
+
@signature = nil
|
21
|
+
|
22
|
+
# Gets the SDK analytics signature query parameter.
|
23
|
+
#
|
24
|
+
# @return [String] The SDK signature query parameter.
|
25
|
+
def sdk_analytics_query_param
|
26
|
+
"#{QUERY_KEY}=#{self.sdk_analytics_signature}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Gets the SDK signature by encoding the SDK version and tech version.
|
30
|
+
#
|
31
|
+
# @return [String] The SDK signature.
|
32
|
+
def sdk_analytics_signature
|
33
|
+
return @signature unless @signature.nil?
|
34
|
+
|
35
|
+
begin
|
36
|
+
@signature = ALGO_VERSION + @product + @sdk_code + encode_version(@sdk_version) + encode_version(@tech_version)
|
37
|
+
rescue RangeError
|
38
|
+
@signature = 'E'
|
39
|
+
end
|
40
|
+
|
41
|
+
@signature
|
42
|
+
end
|
43
|
+
|
44
|
+
# Sets the product code.
|
45
|
+
#
|
46
|
+
# Used for integrations.
|
47
|
+
#
|
48
|
+
# @param [String] product The product code to set. 'A' is for the official SDK. 'B' for integrations.
|
49
|
+
#
|
50
|
+
# @return [void]
|
51
|
+
#
|
52
|
+
# @internal
|
53
|
+
def product(product)
|
54
|
+
@product = product
|
55
|
+
end
|
56
|
+
|
57
|
+
# Sets the SDK code.
|
58
|
+
#
|
59
|
+
# Used for integrations.
|
60
|
+
#
|
61
|
+
# @param [String] sdk_code The SDK code to set.
|
62
|
+
#
|
63
|
+
# @return [void]
|
64
|
+
#
|
65
|
+
# @internal
|
66
|
+
def sdk_code(sdk_code)
|
67
|
+
@sdk_code = sdk_code
|
68
|
+
end
|
69
|
+
|
70
|
+
# Sets the SDK version.
|
71
|
+
#
|
72
|
+
# Used for integrations.
|
73
|
+
#
|
74
|
+
# @param [String] sdk_version The SDK version to set (MAJOR.MINOR.PATCH), for example: "1.0.0".
|
75
|
+
#
|
76
|
+
# @return [void]
|
77
|
+
#
|
78
|
+
# @internal
|
79
|
+
def sdk_version(sdk_version)
|
80
|
+
@sdk_version = sdk_version
|
81
|
+
end
|
82
|
+
|
83
|
+
# Sets the tech version.
|
84
|
+
#
|
85
|
+
# Used for integrations.
|
86
|
+
#
|
87
|
+
# @param [String] tech_version The tech version to set (MAJOR.MINOR), for example: "1.0".
|
88
|
+
#
|
89
|
+
# @return [void]
|
90
|
+
#
|
91
|
+
# @internal
|
92
|
+
def tech_version(tech_version)
|
93
|
+
@tech_version = tech_version.split('.').first(2).join('.')
|
94
|
+
end
|
95
|
+
|
96
|
+
# Encodes a semVer-like version string.
|
97
|
+
#
|
98
|
+
# Example:
|
99
|
+
# input: '1.24.0'
|
100
|
+
# explode: ['1','24','0']
|
101
|
+
# pad: ['01','24','00']
|
102
|
+
# reverse: ['00', '24', '01']
|
103
|
+
# implode: '002401'
|
104
|
+
# int: 2401
|
105
|
+
# binary: '100101100001'
|
106
|
+
# padded: '000000100101100001'
|
107
|
+
# str_split: ['000000', '100101', '100001']
|
108
|
+
# getKey: ['A', 'l', 'h']
|
109
|
+
# implode: 'Alh'
|
110
|
+
#
|
111
|
+
# @param [String] version Can be either x.y.z or x.y
|
112
|
+
#
|
113
|
+
# @return [String] A string built from 3 characters of the base64 table
|
114
|
+
#
|
115
|
+
# @raise [RangeError] when version is larger than 43.21.26
|
116
|
+
def encode_version(version)
|
117
|
+
parts = version.split('.')
|
118
|
+
|
119
|
+
padded_parts = parts.map { |part| part.rjust(2, '0') }
|
120
|
+
number = padded_parts.reverse.join.to_i
|
121
|
+
padded_binary = int_to_padded_bin(number, parts.length * BINARY_PAD_SIZE)
|
122
|
+
|
123
|
+
raise RangeError, 'Version must be smaller than 43.21.26' if padded_binary.length % BINARY_PAD_SIZE != 0
|
124
|
+
|
125
|
+
encoded_chars = padded_binary.chars.each_slice(BINARY_PAD_SIZE).map { |slice| get_key(slice.join) }
|
126
|
+
|
127
|
+
encoded_chars.join
|
128
|
+
end
|
129
|
+
|
130
|
+
# Gets the key for binary value.
|
131
|
+
#
|
132
|
+
# @param [String] binary_value The value.
|
133
|
+
#
|
134
|
+
# @return [Array, Object] The key for the binary value.
|
135
|
+
def get_key(binary_value)
|
136
|
+
@char_codes ||= initialize_char_codes
|
137
|
+
|
138
|
+
@char_codes[binary_value] || ''
|
139
|
+
end
|
140
|
+
|
141
|
+
def initialize_char_codes
|
142
|
+
char_codes = {}
|
143
|
+
CHARS.chars.each_with_index { |char, idx| char_codes[int_to_padded_bin(idx, BINARY_PAD_SIZE)] = char }
|
144
|
+
char_codes
|
145
|
+
end
|
146
|
+
|
147
|
+
# Converts integer to left padded binary string.
|
148
|
+
#
|
149
|
+
# @param [Integer] integer The input.
|
150
|
+
# @param [Integer] pad_num The num of padding chars.
|
151
|
+
#
|
152
|
+
# @return [String] The padded binary string.
|
153
|
+
def int_to_padded_bin(integer, pad_num)
|
154
|
+
integer.to_s(2).rjust(pad_num, '0')
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|