cloudinary 1.9.1 → 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
  4. data/.github/pull_request_template.md +24 -0
  5. data/.gitignore +7 -1
  6. data/.travis.yml +15 -8
  7. data/CHANGELOG.md +261 -0
  8. data/README.md +3 -0
  9. data/Rakefile +3 -45
  10. data/cloudinary.gemspec +27 -20
  11. data/lib/active_storage/blob_key.rb +20 -0
  12. data/lib/active_storage/service/cloudinary_service.rb +249 -0
  13. data/lib/cloudinary.rb +53 -63
  14. data/lib/cloudinary/account_api.rb +231 -0
  15. data/lib/cloudinary/account_config.rb +30 -0
  16. data/lib/cloudinary/api.rb +228 -71
  17. data/lib/cloudinary/auth_token.rb +10 -4
  18. data/lib/cloudinary/base_api.rb +79 -0
  19. data/lib/cloudinary/base_config.rb +70 -0
  20. data/lib/cloudinary/cache.rb +38 -0
  21. data/lib/cloudinary/cache/breakpoints_cache.rb +31 -0
  22. data/lib/cloudinary/cache/key_value_cache_adapter.rb +25 -0
  23. data/lib/cloudinary/cache/rails_cache_adapter.rb +34 -0
  24. data/lib/cloudinary/cache/storage/rails_cache_storage.rb +5 -0
  25. data/lib/cloudinary/carrier_wave.rb +4 -2
  26. data/lib/cloudinary/carrier_wave/remote.rb +3 -2
  27. data/lib/cloudinary/carrier_wave/storage.rb +2 -1
  28. data/lib/cloudinary/{controller.rb → cloudinary_controller.rb} +3 -5
  29. data/lib/cloudinary/config.rb +43 -0
  30. data/lib/cloudinary/helper.rb +77 -7
  31. data/lib/cloudinary/migrator.rb +3 -1
  32. data/lib/cloudinary/railtie.rb +7 -3
  33. data/lib/cloudinary/responsive.rb +111 -0
  34. data/lib/cloudinary/uploader.rb +67 -15
  35. data/lib/cloudinary/utils.rb +324 -54
  36. data/lib/cloudinary/version.rb +1 -1
  37. data/lib/cloudinary/video_helper.rb +96 -22
  38. data/lib/tasks/cloudinary/fetch_assets.rake +48 -0
  39. data/lib/tasks/{cloudinary.rake → cloudinary/sync_static.rake} +0 -0
  40. data/tools/allocate_test_cloud.sh +9 -0
  41. data/tools/get_test_cloud.sh +9 -0
  42. data/tools/update_version +220 -0
  43. data/vendor/assets/javascripts/cloudinary/jquery.cloudinary.js +51 -13
  44. data/vendor/assets/javascripts/cloudinary/jquery.fileupload.js +24 -4
  45. data/vendor/assets/javascripts/cloudinary/jquery.ui.widget.js +741 -561
  46. data/vendor/assets/javascripts/cloudinary/load-image.all.min.js +1 -1
  47. metadata +92 -67
  48. data/spec/access_control_spec.rb +0 -99
  49. data/spec/api_spec.rb +0 -545
  50. data/spec/archive_spec.rb +0 -129
  51. data/spec/auth_token_spec.rb +0 -79
  52. data/spec/cloudinary_helper_spec.rb +0 -190
  53. data/spec/cloudinary_spec.rb +0 -32
  54. data/spec/data/sync_static/app/assets/javascripts/1.coffee +0 -1
  55. data/spec/data/sync_static/app/assets/javascripts/1.js +0 -1
  56. data/spec/data/sync_static/app/assets/stylesheets/1.css +0 -3
  57. data/spec/docx.docx +0 -0
  58. data/spec/favicon.ico +0 -0
  59. data/spec/logo.png +0 -0
  60. data/spec/rake_spec.rb +0 -160
  61. data/spec/sample_asset_file.tsv +0 -4
  62. data/spec/search_spec.rb +0 -109
  63. data/spec/spec_helper.rb +0 -245
  64. data/spec/storage_spec.rb +0 -44
  65. data/spec/streaminig_profiles_api_spec.rb +0 -74
  66. data/spec/support/helpers/temp_file_helpers.rb +0 -22
  67. data/spec/support/shared_contexts/rake.rb +0 -19
  68. data/spec/uploader_spec.rb +0 -363
  69. data/spec/utils_methods_spec.rb +0 -54
  70. data/spec/utils_spec.rb +0 -906
  71. data/spec/video_tag_spec.rb +0 -251
  72. data/spec/video_url_spec.rb +0 -164
@@ -1,7 +1,11 @@
1
1
  require 'digest/md5'
2
2
  require 'cloudinary/video_helper'
3
+ require 'cloudinary/responsive'
3
4
 
4
5
  module CloudinaryHelper
6
+ include ActionView::Helpers::CaptureHelper
7
+ include Responsive
8
+
5
9
  CL_BLANK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
6
10
 
7
11
  # Stand-in for Rails image_tag helper that accepts various options for transformations.
@@ -37,13 +41,45 @@ module CloudinaryHelper
37
41
 
38
42
  end
39
43
 
44
+
45
+ def cl_picture_tag(source, options = {}, sources =[])
46
+
47
+ options = options.clone
48
+ content_tag 'picture' do
49
+ sources.map do |source_def|
50
+ source_options = options.clone
51
+ source_options = Cloudinary::Utils.chain_transformation(source_options, source_def[:transformation])
52
+ source_options[:media] = source_def
53
+ cl_source_tag(source, source_options)
54
+ end.push(cl_image_tag(source, options))
55
+ .join('')
56
+ .html_safe
57
+ end
58
+ end
59
+
60
+ def cl_source_tag(source, options)
61
+ srcset_param = options.fetch(:srcset, {}).merge(Cloudinary.config.srcset || {})
62
+ attributes = options.fetch(:attributes, {}).clone
63
+ responsive_attributes = generate_image_responsive_attributes(source, attributes, srcset_param, options)
64
+ attributes = attributes.merge(responsive_attributes)
65
+ unless attributes.has_key? :srcset
66
+ attributes[:srcset] = Cloudinary::Utils.cloudinary_url(source, options)
67
+ end
68
+ media_attr = generate_media_attribute(options[:media])
69
+ attributes[:media] = media_attr unless media_attr.empty?
70
+ tag "source", attributes, true
71
+ end
72
+
73
+
40
74
  def cloudinary_tag(source, options = {})
41
75
  tag_options = options.clone
42
76
  tag_options[:width] = tag_options.delete(:html_width) if tag_options.include?(:html_width)
43
77
  tag_options[:height] = tag_options.delete(:html_height) if tag_options.include?(:html_height)
44
78
  tag_options[:size] = tag_options.delete(:html_size) if tag_options.include?(:html_size)
45
79
  tag_options[:border] = tag_options.delete(:html_border) if tag_options.include?(:html_border)
46
- source = cloudinary_url_internal(source, tag_options)
80
+ srcset_param = Cloudinary::Utils.config_option_consume(tag_options, :srcset, {})
81
+ src = cloudinary_url_internal(source, tag_options)
82
+ attributes = tag_options.delete(:attributes) || {}
47
83
 
48
84
  responsive_placeholder = Cloudinary::Utils.config_option_consume(tag_options, :responsive_placeholder)
49
85
  client_hints = Cloudinary::Utils.config_option_consume(tag_options, :client_hints)
@@ -51,15 +87,21 @@ module CloudinaryHelper
51
87
  hidpi = tag_options.delete(:hidpi)
52
88
  responsive = tag_options.delete(:responsive)
53
89
  if !client_hints && (hidpi || responsive)
54
- tag_options["data-src"] = source
55
- source = nil
90
+ tag_options["data-src"] = src
91
+ src = nil
56
92
  extra_class = responsive ? "cld-responsive" : "cld-hidpi"
57
93
  tag_options[:class] = [tag_options[:class], extra_class].compact.join(" ")
58
94
  responsive_placeholder = CL_BLANK if responsive_placeholder == "blank"
59
95
  tag_options[:src] = responsive_placeholder
60
96
  end
97
+ responsive_attrs = generate_image_responsive_attributes(source, attributes, srcset_param, options)
98
+ unless responsive_attrs.empty?
99
+ tag_options.delete(:width)
100
+ tag_options.delete(:height)
101
+ tag_options.merge! responsive_attrs
102
+ end
61
103
  if block_given?
62
- yield(source,tag_options)
104
+ yield(src,tag_options)
63
105
  else
64
106
  tag('div', tag_options)
65
107
  end
@@ -244,7 +286,7 @@ module CloudinaryHelper
244
286
  Cloudinary::Utils.private_download_url(public_id, format, options)
245
287
  end
246
288
 
247
- # Helper method that uses the deprecated ZIP download API.
289
+ # Helper method that uses the deprecated ZIP download API.
248
290
  # Replaced by cl_download_zip_url that uses the more advanced and robust archive generation and download API
249
291
  # @deprecated
250
292
  def cl_zip_download_url(tag, options = {})
@@ -254,7 +296,7 @@ module CloudinaryHelper
254
296
  # @see {Cloudinary::Utils.download_archive_url}
255
297
  def cl_download_archive_url(options = {})
256
298
  Cloudinary::Utils.download_archive_url(options)
257
- end
299
+ end
258
300
 
259
301
  # @see {Cloudinary::Utils.download_zip_url}
260
302
  def cl_download_zip_url(options = {})
@@ -276,11 +318,12 @@ module CloudinaryHelper
276
318
  if Cloudinary.config.enhance_image_tag
277
319
  alias_method :image_tag, :image_tag_with_cloudinary
278
320
  alias_method :image_path, :image_path_with_cloudinary
279
- end
321
+ end
280
322
  end
281
323
  end
282
324
 
283
325
  private
326
+
284
327
  def cloudinary_url_internal(source, options = {})
285
328
  options[:ssl_detected] = request.ssl? if defined?(request) && request && request.respond_to?(:ssl?)
286
329
  if defined?(CarrierWave::Uploader::Base) && source.is_a?(CarrierWave::Uploader::Base)
@@ -376,3 +419,30 @@ rescue LoadError
376
419
  # no sass support. Ignore.
377
420
  end
378
421
 
422
+ begin
423
+ require 'sassc'
424
+ require 'sassc/script/functions'
425
+ module SassC::Script::Functions
426
+ # Helper method for generating cloudinary_url in scss files.
427
+ #
428
+ # As opposed to sass(deprecated), optional named arguments are not supported, use hash map instead.
429
+ #
430
+ # Example:
431
+ # Sass: cloudinary-url("sample", $quality: "auto", $fetch_format: "auto");
432
+ # becomes
433
+ # SassC: cloudinary-url("sample", ("quality": "auto", "fetch_format": "auto"));
434
+ #
435
+ # @param [::SassC::Script::Value::String] public_id The public ID of the resource
436
+ # @param [::SassC::Script::Value::Map] sass_options Additional options
437
+ #
438
+ # @return [::SassC::Script::Value::String]
439
+ def cloudinary_url(public_id, sass_options = {})
440
+ options = {}
441
+ sass_options.to_h.each { |k, v| options[k.value.to_sym] = v.value }
442
+ url = Cloudinary::Utils.cloudinary_url(public_id.value, {:type => :asset}.merge(options))
443
+ ::SassC::Script::Value::String.new("url(#{url})")
444
+ end
445
+ end
446
+ rescue LoadError
447
+ # no sassc support. Ignore.
448
+ end
@@ -299,7 +299,9 @@ class Cloudinary::Migrator
299
299
 
300
300
  def debug(message)
301
301
  if @debug
302
- $stderr.print "#{Time.now} Cloudinary::Migrator #{message}\n"
302
+ mutex.synchronize{
303
+ $stderr.print "#{Time.now} Cloudinary::Migrator #{message}\n"
304
+ }
303
305
  end
304
306
  end
305
307
 
@@ -1,8 +1,12 @@
1
1
  class Cloudinary::Railtie < Rails::Railtie
2
2
  rake_tasks do
3
- Dir[File.join(File.dirname(__FILE__),'../tasks/*.rake')].each { |f| load f }
4
- end
3
+ Dir[File.join(File.dirname(__FILE__),'../tasks/**/*.rake')].each { |f| load f }
4
+ end
5
5
  config.after_initialize do |app|
6
6
  ActionView::Base.send :include, CloudinaryHelper
7
7
  end
8
- end
8
+
9
+ ActiveSupport.on_load(:action_controller_base) do
10
+ ActionController::Base.send :include, Cloudinary::CloudinaryController
11
+ end
12
+ end
@@ -0,0 +1,111 @@
1
+ module Responsive
2
+ # Calculate breakpoints for the given configuration
3
+ # @private
4
+ def generate_breakpoints(srcset)
5
+ return srcset[:breakpoints] if srcset[:breakpoints].is_a? Array
6
+ min_width, max_width, max_images = [:min_width, :max_width, :max_images].map {|k| srcset[k]}
7
+ unless [min_width, max_width, max_images].all? {|a| a.is_a? Numeric}
8
+ throw 'Either (min_width, max_width, max_images) or breakpoints must be provided to the image srcset attribute'
9
+ end
10
+ if min_width > max_width
11
+ throw 'min_width must be less than max_width'
12
+ end
13
+
14
+ if max_images <= 0
15
+ throw 'max_images must be a positive integer'
16
+ elsif max_images === 1
17
+ min_width = max_width
18
+ end
19
+ step_size = ((max_width - min_width).to_f / [max_images - 1, 1].max).ceil
20
+ current = min_width
21
+ breakpoints = []
22
+ while current < max_width do
23
+ breakpoints.push(current)
24
+ current += step_size
25
+ end
26
+ breakpoints.push(max_width)
27
+ end
28
+
29
+ # Generate the srcset and sizes attributes
30
+ # @private
31
+ def generate_image_responsive_attributes(public_id, attributes = {}, srcset_data = {}, options = {})
32
+ # Create both srcset and sizes here to avoid fetching breakpoints twice
33
+
34
+ responsive_attributes = {}
35
+ generate_srcset = !attributes[:srcset]
36
+
37
+ if srcset_data.empty?
38
+ return responsive_attributes
39
+ elsif srcset_data.is_a? String
40
+ responsive_attributes[:srcset] = srcset_data
41
+ return responsive_attributes
42
+ end
43
+
44
+ generate_sizes = !attributes[:sizes] && srcset_data[:sizes]
45
+
46
+ if generate_srcset || generate_sizes
47
+ breakpoints = get_or_generate_breakpoints(public_id, srcset_data, options)
48
+
49
+ if generate_srcset
50
+ transformation = srcset_data[:transformation]
51
+ srcset_attr = generate_srcset_attribute(public_id, breakpoints, transformation, options)
52
+ responsive_attributes[:srcset] = srcset_attr unless srcset_attr.empty?
53
+ end
54
+
55
+ if generate_sizes
56
+ sizes_attr = generate_sizes_attribute(breakpoints)
57
+ responsive_attributes[:sizes] = sizes_attr unless sizes_attr.empty?
58
+ end
59
+ end
60
+
61
+ responsive_attributes
62
+ end
63
+
64
+ # If cache is enabled, get the breakpoints from the cache. If the values were not found in the cache,
65
+ # or cache is not enabled, generate the values.
66
+ # @private
67
+ def get_or_generate_breakpoints(public_id, srcset, options)
68
+ if srcset[:use_cache]
69
+ Cloudinary::Cache.get(public_id, options) || []
70
+ else
71
+ generate_breakpoints(srcset)
72
+ end
73
+ end
74
+
75
+ # Generate a resource URL scaled to the given width
76
+ # @private
77
+ def generate_scaled_url(public_id, width, transformation={}, options={})
78
+ config_params = Cloudinary::Utils.extract_config_params(options)
79
+ transformation ||= options
80
+ config_params[:raw_transformation] = Cloudinary::Utils.generate_transformation_string(
81
+ [transformation.clone, {:crop => 'scale', :width => width}].reject(&:blank?))
82
+ config_params.delete :width
83
+ config_params.delete :height
84
+ Cloudinary::Utils.cloudinary_url public_id, config_params
85
+ end
86
+
87
+ # Generate srcset attribute value of the HTML img tag
88
+ # @private
89
+ def generate_srcset_attribute(public_id, breakpoints, transformation={}, options={})
90
+ options = options.clone
91
+ Cloudinary::Utils.patch_fetch_format(options)
92
+ breakpoints.map{|width| "#{generate_scaled_url(public_id, width, transformation, options)} #{width}w"}.join(', ').html_safe
93
+ end
94
+
95
+ # Generate media attribute value of the HTML img tag
96
+ # @private
97
+ def generate_media_attribute(options)
98
+ options ||= {}
99
+ [:min_width, :max_width]
100
+ .select {|name| options[name]}
101
+ .map {|name| "(#{name.to_s.tr('_', '-')}: #{options[name]}px)"}
102
+ .join(' and ').html_safe
103
+ end
104
+
105
+
106
+ # Generate the sizes attribute
107
+ # @private
108
+ def generate_sizes_attribute(breakpoints)
109
+ breakpoints.map{|width| "(max-width: #{width}px) #{width}px"}.join(', ')
110
+ end
111
+ end
@@ -1,11 +1,11 @@
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
 
7
- REMOTE_URL_REGEX = %r(^ftp:|^https?:|^s3:|^data:[^;]*;base64,([a-zA-Z0-9\/+\n=]+)$)
8
-
8
+ REMOTE_URL_REGEX = Cloudinary::Utils::REMOTE_URL_REGEX
9
9
  # @deprecated use {Cloudinary::Utils.build_eager} instead
10
10
  def self.build_eager(eager)
11
11
  Cloudinary::Utils.build_eager(eager)
@@ -27,6 +27,7 @@ class Cloudinary::Uploader
27
27
  :backup => Cloudinary::Utils.as_safe_bool(options[:backup]),
28
28
  :callback => options[:callback],
29
29
  :categorization => options[:categorization],
30
+ :cinemagraph_analysis => Cloudinary::Utils.as_safe_bool(options[:cinemagraph_analysis]),
30
31
  :colors => Cloudinary::Utils.as_safe_bool(options[:colors]),
31
32
  :context => Cloudinary::Utils.encode_context(options[:context]),
32
33
  :custom_coordinates => Cloudinary::Utils.encode_double_array(options[:custom_coordinates]),
@@ -36,10 +37,12 @@ class Cloudinary::Uploader
36
37
  :eager_async => Cloudinary::Utils.as_safe_bool(options[:eager_async]),
37
38
  :eager_notification_url => options[:eager_notification_url],
38
39
  :exif => Cloudinary::Utils.as_safe_bool(options[:exif]),
40
+ :eval => options[:eval],
39
41
  :face_coordinates => Cloudinary::Utils.encode_double_array(options[:face_coordinates]),
40
42
  :faces => Cloudinary::Utils.as_safe_bool(options[:faces]),
41
43
  :folder => options[:folder],
42
44
  :format => options[:format],
45
+ :filename_override => options[:filename_override],
43
46
  :headers => build_custom_headers(options[:headers]),
44
47
  :image_metadata => Cloudinary::Utils.as_safe_bool(options[:image_metadata]),
45
48
  :invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate]),
@@ -50,6 +53,8 @@ class Cloudinary::Uploader
50
53
  :phash => Cloudinary::Utils.as_safe_bool(options[:phash]),
51
54
  :proxy => options[:proxy],
52
55
  :public_id => options[:public_id],
56
+ :quality_analysis => Cloudinary::Utils.as_safe_bool(options[:quality_analysis]),
57
+ :quality_override => options[:quality_override],
53
58
  :raw_convert => options[:raw_convert],
54
59
  :responsive_breakpoints => Cloudinary::Utils.generate_responsive_breakpoints_string(options[:responsive_breakpoints]),
55
60
  :return_delete_token => Cloudinary::Utils.as_safe_bool(options[:return_delete_token]),
@@ -61,6 +66,8 @@ class Cloudinary::Uploader
61
66
  :unique_filename => Cloudinary::Utils.as_safe_bool(options[:unique_filename]),
62
67
  :upload_preset => options[:upload_preset],
63
68
  :use_filename => Cloudinary::Utils.as_safe_bool(options[:use_filename]),
69
+ :accessibility_analysis => Cloudinary::Utils.as_safe_bool(options[:accessibility_analysis]),
70
+ :metadata => Cloudinary::Utils.encode_context(options[:metadata])
64
71
  }
65
72
  params
66
73
  end
@@ -74,7 +81,10 @@ class Cloudinary::Uploader
74
81
  params = build_upload_params(options)
75
82
  if file.is_a?(Pathname)
76
83
  params[:file] = File.open(file, "rb")
77
- elsif file.respond_to?(:read) || file.match(REMOTE_URL_REGEX)
84
+ elsif file.is_a?(StringIO)
85
+ file.rewind
86
+ params[:file] = Cloudinary::Blob.new(file.read, options)
87
+ elsif file.respond_to?(:read) || Cloudinary::Utils.is_remote?(file)
78
88
  params[:file] = file
79
89
  else
80
90
  params[:file] = File.open(file, "rb")
@@ -92,7 +102,7 @@ class Cloudinary::Uploader
92
102
  public_id = public_id_or_options
93
103
  options = old_options
94
104
  end
95
- if file.match(REMOTE_URL_REGEX)
105
+ if Cloudinary::Utils.is_remote?(file)
96
106
  return upload(file, options.merge(:public_id => public_id))
97
107
  elsif file.is_a?(Pathname) || !file.respond_to?(:read)
98
108
  filename = file
@@ -100,6 +110,7 @@ class Cloudinary::Uploader
100
110
  else
101
111
  filename = "cloudinaryfile"
102
112
  end
113
+ unique_upload_id = Cloudinary::Utils.random_public_id
103
114
  upload = nil
104
115
  index = 0
105
116
  chunk_size = options[:chunk_size] || 20_000_000
@@ -107,8 +118,7 @@ class Cloudinary::Uploader
107
118
  buffer = file.read(chunk_size)
108
119
  current_loc = index*chunk_size
109
120
  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"]
121
+ 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
122
  index += 1
113
123
  end
114
124
  upload
@@ -245,7 +255,7 @@ class Cloudinary::Uploader
245
255
  end
246
256
  end
247
257
 
248
- # options may include 'exclusive' (boolean) which causes clearing this tag from all other resources
258
+ # options may include 'exclusive' (boolean) which causes clearing this tag from all other resources
249
259
  def self.add_tag(tag, public_ids = [], options = {})
250
260
  exclusive = options.delete(:exclusive)
251
261
  command = exclusive ? "set_exclusive" : "add"
@@ -264,6 +274,29 @@ class Cloudinary::Uploader
264
274
  return self.call_tags_api(nil, "remove_all", public_ids, options)
265
275
  end
266
276
 
277
+ # Populates metadata fields with the given values. Existing values will be overwritten.
278
+ #
279
+ # Any metadata-value pairs given are merged with any existing metadata-value pairs
280
+ # (an empty value for an existing metadata field clears the value).
281
+ #
282
+ # @param [Hash] metadata A list of custom metadata fields (by external_id) and the values to assign to each of them.
283
+ # @param [Array] public_ids An array of Public IDs of assets uploaded to Cloudinary.
284
+ # @param [Hash] options
285
+ # @option options [String] :resource_type The type of file. Default: image. Valid values: image, raw, video.
286
+ # @option options [String] :type The storage type. Default: upload. Valid values: upload, private, authenticated
287
+ # @return mixed a list of public IDs that were updated
288
+ # @raise [Cloudinary::Api:Error]
289
+ def self.update_metadata(metadata, public_ids, options = {})
290
+ self.call_api("metadata", options) do
291
+ {
292
+ timestamp: (options[:timestamp] || Time.now.to_i),
293
+ type: options[:type],
294
+ public_ids: Cloudinary::Utils.build_array(public_ids),
295
+ metadata: Cloudinary::Utils.encode_context(metadata)
296
+ }
297
+ end
298
+ end
299
+
267
300
  private
268
301
 
269
302
  def self.call_tags_api(tag, command, public_ids = [], options = {})
@@ -292,7 +325,7 @@ class Cloudinary::Uploader
292
325
  return call_api("context", options) do
293
326
  {
294
327
  :timestamp => (options[:timestamp] || Time.now.to_i),
295
- :context => Cloudinary::Utils.encode_hash(context),
328
+ :context => Cloudinary::Utils.encode_context(context),
296
329
  :public_ids => Cloudinary::Utils.build_array(public_ids),
297
330
  :command => command,
298
331
  :type => options[:type]
@@ -303,25 +336,29 @@ class Cloudinary::Uploader
303
336
  def self.call_api(action, options)
304
337
  options = options.clone
305
338
  return_error = options.delete(:return_error)
339
+ use_cache = options[:use_cache] || Cloudinary.config.use_cache
306
340
 
307
341
  params, non_signable = yield
308
342
  non_signable ||= []
309
343
 
310
344
  unless options[:unsigned]
311
- api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
312
- api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
313
- params[:signature] = Cloudinary::Utils.api_sign_request(params.reject { |k, v| non_signable.include?(k) }, api_secret)
314
- params[:api_key] = api_key
345
+ api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
346
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
347
+ signature_algorithm = options[:signature_algorithm]
348
+ params[:signature] = Cloudinary::Utils.api_sign_request(params.reject { |k, v| non_signable.include?(k) }, api_secret, signature_algorithm)
349
+ params[:api_key] = api_key
315
350
  end
316
- timeout = options[:timeout] || Cloudinary.config.timeout || 60
351
+ proxy = options[:api_proxy] || Cloudinary.config.api_proxy
352
+ timeout = options.fetch(:timeout) { Cloudinary.config.to_h.fetch(:timeout, 60) }
317
353
 
318
354
  result = nil
319
355
 
320
356
  api_url = Cloudinary::Utils.cloudinary_api_url(action, options)
321
357
  headers = { "User-Agent" => Cloudinary::USER_AGENT }
322
358
  headers['Content-Range'] = options[:content_range] if options[:content_range]
359
+ headers['X-Unique-Upload-Id'] = options[:unique_upload_id] if options[:unique_upload_id]
323
360
  headers.merge!(options[:extra_headers]) if options[:extra_headers]
324
- RestClient::Request.execute(:method => :post, :url => api_url, :payload => params.reject { |k, v| v.nil? || v=="" }, :timeout => timeout, :headers => headers) do
361
+ RestClient::Request.execute(:method => :post, :url => api_url, :payload => params.reject { |k, v| v.nil? || v=="" }, :timeout => timeout, :headers => headers, :proxy => proxy) do
325
362
  |response, request, tmpresult|
326
363
  raise CloudinaryException, "Server returned unexpected status code - #{response.code} - #{response.body}" unless [200, 400, 401, 403, 404, 500].include?(response.code)
327
364
  begin
@@ -338,11 +375,26 @@ class Cloudinary::Uploader
338
375
  end
339
376
  end
340
377
  end
341
-
378
+ if use_cache && !result.nil?
379
+ cache_results(result)
380
+ end
342
381
  result
343
382
  end
344
383
 
345
384
  def self.build_custom_headers(headers)
346
385
  Array(headers).map { |*a| a.join(": ") }.join("\n")
347
386
  end
387
+
388
+ def self.cache_results(result)
389
+ if result["responsive_breakpoints"]
390
+ result["responsive_breakpoints"].each do |bp|
391
+ Cloudinary::Cache.set(
392
+ result["public_id"],
393
+ {type: result["type"], resource_type: result["resource_type"], raw_transformation: bp["transformation"]},
394
+ bp["breakpoints"].map{|o| o['width']}
395
+ )
396
+ end
397
+ end
398
+
399
+ end
348
400
  end