cloudinary 1.9.1 → 1.10.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.
@@ -1,6 +1,7 @@
1
1
  # Copyright Cloudinary
2
2
  require 'rest_client'
3
3
  require 'json'
4
+ require 'cloudinary/cache'
4
5
 
5
6
  class Cloudinary::Uploader
6
7
 
@@ -100,6 +101,7 @@ class Cloudinary::Uploader
100
101
  else
101
102
  filename = "cloudinaryfile"
102
103
  end
104
+ unique_upload_id = Cloudinary::Utils.random_public_id
103
105
  upload = nil
104
106
  index = 0
105
107
  chunk_size = options[:chunk_size] || 20_000_000
@@ -107,8 +109,7 @@ class Cloudinary::Uploader
107
109
  buffer = file.read(chunk_size)
108
110
  current_loc = index*chunk_size
109
111
  range = "bytes #{current_loc}-#{current_loc+buffer.size - 1}/#{file.size}"
110
- upload = upload_large_part(Cloudinary::Blob.new(buffer, :original_filename => filename), options.merge(:public_id => public_id, :content_range => range))
111
- public_id = upload["public_id"]
112
+ upload = upload_large_part(Cloudinary::Blob.new(buffer, :original_filename => filename), options.merge(:public_id => public_id, :unique_upload_id => unique_upload_id, :content_range => range))
112
113
  index += 1
113
114
  end
114
115
  upload
@@ -292,7 +293,7 @@ class Cloudinary::Uploader
292
293
  return call_api("context", options) do
293
294
  {
294
295
  :timestamp => (options[:timestamp] || Time.now.to_i),
295
- :context => Cloudinary::Utils.encode_hash(context),
296
+ :context => Cloudinary::Utils.encode_context(context),
296
297
  :public_ids => Cloudinary::Utils.build_array(public_ids),
297
298
  :command => command,
298
299
  :type => options[:type]
@@ -303,6 +304,7 @@ class Cloudinary::Uploader
303
304
  def self.call_api(action, options)
304
305
  options = options.clone
305
306
  return_error = options.delete(:return_error)
307
+ use_cache = options[:use_cache] || Cloudinary.config.use_cache
306
308
 
307
309
  params, non_signable = yield
308
310
  non_signable ||= []
@@ -320,6 +322,7 @@ class Cloudinary::Uploader
320
322
  api_url = Cloudinary::Utils.cloudinary_api_url(action, options)
321
323
  headers = { "User-Agent" => Cloudinary::USER_AGENT }
322
324
  headers['Content-Range'] = options[:content_range] if options[:content_range]
325
+ headers['X-Unique-Upload-Id'] = options[:unique_upload_id] if options[:unique_upload_id]
323
326
  headers.merge!(options[:extra_headers]) if options[:extra_headers]
324
327
  RestClient::Request.execute(:method => :post, :url => api_url, :payload => params.reject { |k, v| v.nil? || v=="" }, :timeout => timeout, :headers => headers) do
325
328
  |response, request, tmpresult|
@@ -338,11 +341,26 @@ class Cloudinary::Uploader
338
341
  end
339
342
  end
340
343
  end
341
-
344
+ if use_cache && !result.nil?
345
+ cache_results(result)
346
+ end
342
347
  result
343
348
  end
344
349
 
345
350
  def self.build_custom_headers(headers)
346
351
  Array(headers).map { |*a| a.join(": ") }.join("\n")
347
352
  end
353
+
354
+ def self.cache_results(result)
355
+ if result["responsive_breakpoints"]
356
+ result["responsive_breakpoints"].each do |bp|
357
+ Cloudinary::Cache.set(
358
+ result["public_id"],
359
+ {type: result["type"], resource_type: result["resource_type"], raw_transformation: bp["transformation"]},
360
+ bp["breakpoints"].map{|o| o['width']}
361
+ )
362
+ end
363
+ end
364
+
365
+ end
348
366
  end
@@ -6,6 +6,7 @@ require 'aws_cf_signer'
6
6
  require 'json'
7
7
  require 'cgi'
8
8
  require 'cloudinary/auth_token'
9
+ require 'cloudinary/responsive'
9
10
 
10
11
  class Cloudinary::Utils
11
12
  # @deprecated Use Cloudinary::SHARED_CDN
@@ -40,13 +41,100 @@ class Cloudinary::Utils
40
41
  "tags" => "tags",
41
42
  "width" => "w"
42
43
  }
44
+
45
+ URL_KEYS = %w[
46
+ api_secret
47
+ auth_token
48
+ cdn_subdomain
49
+ cloud_name
50
+ cname
51
+ format
52
+ private_cdn
53
+ resource_type
54
+ secure
55
+ secure_cdn_subdomain
56
+ secure_distribution
57
+ shorten
58
+ sign_url
59
+ ssl_detected
60
+ type
61
+ url_suffix
62
+ use_root_path
63
+ version
64
+ ].map(&:to_sym)
65
+
66
+
67
+ TRANSFORMATION_PARAMS = %w[
68
+ angle
69
+ aspect_ratio
70
+ audio_codec
71
+ audio_frequency
72
+ background
73
+ bit_rate
74
+ border
75
+ color
76
+ color_space
77
+ crop
78
+ custom_function
79
+ default_image
80
+ delay
81
+ density
82
+ dpr
83
+ duration
84
+ effect
85
+ end_offset
86
+ fetch_format
87
+ flags
88
+ gravity
89
+ height
90
+ if
91
+ keyframe_interval
92
+ offset
93
+ opacity
94
+ overlay
95
+ page
96
+ prefix
97
+ quality
98
+ radius
99
+ raw_transformation
100
+ responsive_width
101
+ size
102
+ start_offset
103
+ streaming_profile
104
+ transformation
105
+ underlay
106
+ variables
107
+ video_codec
108
+ video_sampling
109
+ width
110
+ x
111
+ y
112
+ zoom
113
+ ].map(&:to_sym)
114
+
115
+ def self.extract_config_params(options)
116
+ options.select{|k,v| URL_KEYS.include?(k)}
117
+ end
118
+
119
+ def self.extract_transformation_params(options)
120
+ options.select{|k,v| TRANSFORMATION_PARAMS.include?(k)}
121
+ end
122
+
123
+ def self.chain_transformation(options, *transformation)
124
+ base_options = extract_config_params(options)
125
+ transformation = transformation.reject(&:nil?)
126
+ base_options[:transformation] = build_array(extract_transformation_params(options)).concat(transformation)
127
+ base_options
128
+ end
129
+
130
+
43
131
  # Warning: options are being destructively updated!
44
132
  def self.generate_transformation_string(options={}, allow_implicit_crop_mode = false)
45
133
  # allow_implicit_crop_mode was added to support height and width parameters without specifying a crop mode.
46
134
  # This only apply to this (cloudinary_gem) SDK
47
135
 
48
136
  if options.is_a?(Array)
49
- return options.map{|base_transformation| generate_transformation_string(base_transformation.clone, allow_implicit_crop_mode)}.join("/")
137
+ return options.map{|base_transformation| generate_transformation_string(base_transformation.clone, allow_implicit_crop_mode)}.reject(&:blank?).join("/")
50
138
  end
51
139
 
52
140
  symbolize_keys!(options)
@@ -105,6 +193,7 @@ class Cloudinary::Utils
105
193
  overlay = process_layer(options.delete(:overlay))
106
194
  underlay = process_layer(options.delete(:underlay))
107
195
  ifValue = process_if(options.delete(:if))
196
+ custom_function = process_custom_function(options.delete(:custom_function))
108
197
 
109
198
  params = {
110
199
  :a => normalize_expression(angle),
@@ -116,6 +205,7 @@ class Cloudinary::Utils
116
205
  :dpr => normalize_expression(dpr),
117
206
  :e => normalize_expression(effect),
118
207
  :fl => flags,
208
+ :fn => custom_function,
119
209
  :h => normalize_expression(height),
120
210
  :l => overlay,
121
211
  :o => normalize_expression(options.delete(:opacity)),
@@ -230,9 +320,13 @@ class Cloudinary::Utils
230
320
  text_style = nil
231
321
  components = []
232
322
 
233
- unless public_id.blank?
234
- public_id = public_id.gsub("/", ":")
235
- public_id = "#{public_id}.#{format}" unless format.nil?
323
+ if public_id.present?
324
+ if type == "fetch" && public_id.match(%r(^https?:/)i)
325
+ public_id = Base64.urlsafe_encode64(public_id)
326
+ else
327
+ public_id = public_id.gsub("/", ":")
328
+ public_id = "#{public_id}.#{format}" if format
329
+ end
236
330
  end
237
331
 
238
332
  if text.blank? && resource_type != "text"
@@ -348,9 +442,9 @@ class Cloudinary::Utils
348
442
  # Warning: options are being destructively updated!
349
443
  def self.unsigned_download_url(source, options = {})
350
444
 
445
+ patch_fetch_format(options)
351
446
  type = options.delete(:type)
352
447
 
353
- options[:fetch_format] ||= options.delete(:format) if type.to_s == "fetch"
354
448
  transformation = self.generate_transformation_string(options)
355
449
 
356
450
  resource_type = options.delete(:resource_type)
@@ -410,6 +504,7 @@ class Cloudinary::Utils
410
504
 
411
505
  transformation = transformation.gsub(%r(([^:])//), '\1/')
412
506
  if sign_url && ( !auth_token || auth_token.empty?)
507
+ raise(CloudinaryException, "Must supply api_secret") if (secret.nil? || secret.empty?)
413
508
  to_sign = [transformation, sign_version && version, source_to_sign].reject(&:blank?).join("/")
414
509
  to_sign = fully_unescape(to_sign)
415
510
  signature = 's--' + Base64.urlsafe_encode64(Digest::SHA1.digest(to_sign + secret))[0,8] + '--'
@@ -968,4 +1063,26 @@ class Cloudinary::Utils
968
1063
  end
969
1064
  private_class_method :process_video_params
970
1065
 
1066
+ def self.process_custom_function(param)
1067
+ return param unless param.is_a? Hash
1068
+
1069
+ function_type = param[:function_type]
1070
+ source = param[:source]
1071
+
1072
+ source = Base64.urlsafe_encode64(source) if function_type == "remote"
1073
+ "#{function_type}:#{source}"
1074
+ end
1075
+
1076
+ #
1077
+ # Handle the format parameter for fetch urls
1078
+ # @private
1079
+ # @param options url and transformation options. This argument may be changed by the function!
1080
+ #
1081
+ def self.patch_fetch_format(options={})
1082
+ if options[:type] === :fetch
1083
+ format_arg = options.delete(:format)
1084
+ options[:fetch_format] ||= format_arg
1085
+ end
1086
+ end
1087
+
971
1088
  end
@@ -1,4 +1,4 @@
1
1
  # Copyright Cloudinary
2
2
  module Cloudinary
3
- VERSION = "1.9.1"
3
+ VERSION = "1.10.0"
4
4
  end
@@ -9,6 +9,9 @@ def days(n)
9
9
  end
10
10
 
11
11
  describe "Access Control" do
12
+ before :each do
13
+ Cloudinary.reset_config
14
+ end
12
15
  let (:acl) {{
13
16
  :access_type => 'anonymous',
14
17
  :start => '2019-02-22 16:20:57 +0200',
@@ -202,9 +202,9 @@ describe Cloudinary::Api do
202
202
 
203
203
  describe 'transformations' do
204
204
  it "should allow listing transformations" do
205
- transformation = @api.transformations(max_results: 500)["transformations"].find { |transformation| transformation["name"] == TEST_TRANSFOMATION }
206
- expect(transformation).not_to be_blank
207
- expect(transformation["used"]).to eq(true)
205
+ transformations = @api.transformations()["transformations"]
206
+ expect(transformations[0]).not_to be_empty
207
+ expect(transformations[0]["used"]).to eq(true)
208
208
  end
209
209
 
210
210
  it "should allow getting transformation metadata" do
@@ -269,7 +269,7 @@ describe Cloudinary::Api do
269
269
  expect(RestClient::Request).to receive(:execute).with(deep_hash_value( [:payload, :named ]=> true))
270
270
  @api.transformations :named => true
271
271
  end
272
-
272
+
273
273
  end
274
274
  it "should allow deleting implicit transformation" do
275
275
  @api.transformation(TEST_TRANSFOMATION)
@@ -467,12 +467,12 @@ describe Cloudinary::Api do
467
467
 
468
468
  expect(result["published"]).to be_an_instance_of(Array)
469
469
  expect(result["published"].length).to eq(1)
470
-
470
+
471
471
  resource = result["published"][0]
472
-
472
+
473
473
  expect(resource["public_id"]).to eq(publicId)
474
474
  expect(resource["type"]).to eq('upload')
475
-
475
+
476
476
  bytes = resource["bytes"]
477
477
  end
478
478
  it "should publish resources by prefix and overwrite" do
@@ -480,13 +480,13 @@ describe Cloudinary::Api do
480
480
 
481
481
  expect(result["published"]).to be_an_instance_of(Array)
482
482
  expect(result["published"].length).to eq(1)
483
-
483
+
484
484
  resource = result["published"][0]
485
-
485
+
486
486
  expect(resource["public_id"]).to eq(publicId)
487
487
  expect(resource["bytes"]).not_to eq(bytes)
488
488
  expect(resource["type"]).to eq('upload')
489
-
489
+
490
490
  bytes = resource["bytes"]
491
491
  end
492
492
  it "should publish resources by tag and overwrite" do
@@ -494,16 +494,22 @@ describe Cloudinary::Api do
494
494
 
495
495
  expect(result["published"]).to be_an_instance_of(Array)
496
496
  expect(result["published"].length).to eq(1)
497
-
497
+
498
498
  resource = result["published"][0]
499
-
499
+
500
500
  expect(resource["public_id"]).to eq(publicId)
501
501
  expect(resource["bytes"]).not_to eq(bytes)
502
502
  expect(resource["type"]).to eq('upload')
503
-
503
+
504
504
  bytes = resource["bytes"]
505
505
  end
506
506
  end
507
+ describe "json breakpoints" do
508
+ it "should retrieve breakpoints as json array" do
509
+ bp = Cloudinary::Api.get_breakpoints(test_id_1, srcset: {min_width:10, max_width:2000, bytes_step: 10, max_images: 20})
510
+ expect(bp).to be_truthy
511
+ end
512
+ end
507
513
  end
508
514
 
509
515
  describe Cloudinary::Api::Response do
@@ -3,25 +3,23 @@ require 'spec_helper'
3
3
  require 'cloudinary'
4
4
 
5
5
  describe 'auth_token' do
6
- before :all do
7
- @url_backup = ENV["CLOUDINARY_URL"]
8
- end
9
- before do
10
- Cloudinary.config_from_url "cloudinary://a:b@test123"
6
+
7
+ before :each do
8
+ Cloudinary.reset_config
9
+ Cloudinary.config_from_url 'cloudinary://a:b@test123'
11
10
  Cloudinary.config.auth_token = { :key => KEY, :duration => 300, :start_time => 11111111 }
12
11
  end
13
- after do
14
- ENV["CLOUDINARY_URL"] = @url_backup
15
- Cloudinary::config_from_url @url_backup
16
- end
17
12
  it "should generate with start and duration" do
18
13
  token = Cloudinary::Utils.generate_auth_token :start_time => 1111111111, :acl => "/image/*", :duration => 300
19
14
  expect(token).to eq '__cld_token__=st=1111111111~exp=1111111411~acl=%2fimage%2f*~hmac=1751370bcc6cfe9e03f30dd1a9722ba0f2cdca283fa3e6df3342a00a7528cc51'
20
15
  end
21
16
 
22
17
  describe "authenticated url" do
23
- before do
18
+ before :each do
19
+ Cloudinary.class_variable_set :@@config, nil
20
+ Cloudinary.config_from_url 'cloudinary://a:b@test123'
24
21
  Cloudinary.config :private_cdn => true
22
+ Cloudinary.config.auth_token = { :key => KEY, :duration => 300, :start_time => 11111111 }
25
23
 
26
24
  end
27
25
  it "should add token if authToken is globally set and signed = true" do
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+ require 'cloudinary'
3
+ require 'cloudinary/cache'
4
+ require 'rspec'
5
+ require 'active_support/cache'
6
+
7
+ describe 'Responsive cache' do
8
+
9
+ before :all do
10
+ Cloudinary.reset_config
11
+ unless defined? Rails and defined? Rails.cache
12
+ module Rails
13
+ class << self
14
+ attr_accessor :cache
15
+ end
16
+ Rails.cache = ActiveSupport::Cache::FileStore.new("#{Dir.getwd}/../tmp/cache")
17
+ end
18
+ end
19
+
20
+ Cloudinary::config.use_cache = true
21
+ Cloudinary::Cache.storage= Rails.cache
22
+ @i=0
23
+ end
24
+
25
+ after :all do
26
+ # Rails.cache.clear
27
+ end
28
+
29
+ def get_cache
30
+ Rails.cache.fetch CACHE_KEY do
31
+ @i = @i + 1
32
+ end
33
+ end
34
+ it 'should cache breakpoints' do
35
+ j = get_cache
36
+ j = get_cache
37
+
38
+ expect(j).to eql(1)
39
+ expect(@i).to eql(1)
40
+ end
41
+
42
+ it 'should cache upload results' do
43
+ result = Cloudinary::Uploader.upload(
44
+ TEST_IMG,
45
+ :tags => [TEST_TAG, TIMESTAMP_TAG],
46
+ responsive_breakpoints: [
47
+ {
48
+ create_derived: false,
49
+ transformation: {
50
+ angle: 90
51
+ },
52
+ format: 'gif'
53
+ },
54
+ {
55
+ create_derived: false,
56
+ transformation: {angle: 45, crop: 'scale'},
57
+ format: 'png'
58
+ },
59
+ {
60
+ create_derived: false,
61
+ }
62
+ ]
63
+ )
64
+ expect(result["responsive_breakpoints"]).to_not be_nil
65
+ expect(result["responsive_breakpoints"].length).to_not eql(0)
66
+ result["responsive_breakpoints"].each do |bp|
67
+ bp = Cloudinary::Cache.get(
68
+ result["public_id"],
69
+ {
70
+ type: bp["type"],
71
+ resource_type: bp["resource_type"],
72
+ raw_transformation: bp["transformation"]})
73
+ expect(bp).to_not be_nil
74
+ end
75
+ end
76
+ describe Cloudinary::Uploader do
77
+ let (:options) { {
78
+ :tags => [TEST_TAG, TIMESTAMP_TAG],
79
+ :use_cache => true,
80
+ :responsive_breakpoints => [
81
+ {
82
+ :create_derived => false,
83
+ :transformation => {
84
+ :angle => 90
85
+ },
86
+ :format => 'gif'
87
+ },
88
+ {
89
+ :create_derived => false,
90
+ :transformation => ResponsiveTest::TRANSFORMATION,
91
+ :format => ResponsiveTest::FORMAT
92
+ },
93
+ {
94
+ :create_derived => false
95
+ }
96
+ ]
97
+ }}
98
+ before :all do
99
+ end
100
+
101
+ it "Should save responsive breakpoints to cache after upload" do
102
+ result = Cloudinary::Uploader.upload( TEST_IMG, options)
103
+ cache_value = Cloudinary::Cache.get(result["public_id"], transformation: ResponsiveTest::TRANSFORMATION, format: ResponsiveTest::FORMAT)
104
+
105
+ expect(cache_value).to eql(ResponsiveTest::IMAGE_BP_VALUES)
106
+ end
107
+ end
108
+
109
+ end