cloudinary 1.9.1 → 1.20.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.
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