cloudinary 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 154b7abbf7ae3c462f1277f49dfed3cfdd4ef60c
4
- data.tar.gz: 56a961c58cc636da0830ba4e1db3f486fe1dffd4
3
+ metadata.gz: 7614adaac6c80b6af25b391e1224a492c609fde8
4
+ data.tar.gz: 726d2520ec2adbb6d91b5ec6b1bceb46071d58e9
5
5
  SHA512:
6
- metadata.gz: 7fc132e944b87f3bd829f3e45a16609c7e4743a8fa331fad6d62a490a6f1625c533ea26b9eb50d1da71123abc5948166d5b606694b4765562877d255f54f93e9
7
- data.tar.gz: 5a1ca4908473ea368ed4a2c1753852879fc75bf3dba22819f44637728de83c6a69f0a48482b5729e46d9f582a3c70748deeaad5dc683a8a2fa831ec784c3b302
6
+ metadata.gz: 760b8e34412a2e4272edc42032aa6abf92cb655472dccd4c35e39126adbce44660b44a0210956ebf28708bc5bb234504f17799023b7062bf6812c257d7aca072
7
+ data.tar.gz: 9b8869009e05cb9ee5b100cd5652a49e653aedd992dc350a55375a527dff0b5209d0fdadce221d8c7253a3c4b7dca66d2e5b8bcfcd1fb469b8bfa692d562604a
@@ -1,4 +1,18 @@
1
1
 
2
+ 1.8.0 / 2017-05-01
3
+ ==================
4
+
5
+ New functionality and features
6
+ ------------------------------
7
+
8
+ * Add Search API
9
+ * Sync static for non image assets (#241) fixes #27
10
+
11
+ Other Changes
12
+ -------------
13
+
14
+ * Fix Carrierwave signed URL.
15
+
2
16
  1.7.0 / 2017-04-09
3
17
  ==================
4
18
 
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.add_dependency "aws_cf_signer"
24
24
  s.add_development_dependency "rspec", '>=3.5'
25
25
  s.add_development_dependency "rspec-rails"
26
+ s.add_development_dependency "rake"
26
27
 
27
28
  if RUBY_VERSION > "2.0"
28
29
  s.add_dependency "rest-client"
@@ -22,6 +22,7 @@ module Cloudinary
22
22
  autoload :PreloadedFile, "cloudinary/preloaded_file"
23
23
  autoload :Static, "cloudinary/static"
24
24
  autoload :CarrierWave, "cloudinary/carrier_wave"
25
+ autoload :Search, "cloudinary/search"
25
26
 
26
27
  CF_SHARED_CDN = "d3jpl91pxevbkh.cloudfront.net"
27
28
  AKAMAI_SHARED_CDN = "res.cloudinary.com"
@@ -1,4 +1,5 @@
1
1
  require 'rest_client'
2
+ require 'json'
2
3
 
3
4
  class Cloudinary::Api
4
5
  class Error < CloudinaryException; end
@@ -316,7 +317,14 @@ class Cloudinary::Api
316
317
  # Add authentication
317
318
  api_url.sub!(%r(^(https?://)), "\\1#{api_key}:#{api_secret}@")
318
319
 
319
- RestClient::Request.execute(:method => method, :url => api_url, :payload => params.reject { |k, v| v.nil? || v=="" }, :timeout => timeout, :headers => { "User-Agent" => Cloudinary::USER_AGENT }) do
320
+ headers = { "User-Agent" => Cloudinary::USER_AGENT }
321
+ if options[:content_type]== :json
322
+ payload = params.to_json
323
+ headers.merge!("Content-Type"=> 'application/json', "Accept"=> 'application/json')
324
+ else
325
+ payload = params.reject { |k, v| v.nil? || v=="" }
326
+ end
327
+ RestClient::Request.execute(:method => method, :url => api_url, :payload => payload, :timeout => timeout, :headers => headers) do
320
328
  |response, request, tmpresult|
321
329
  return Response.new(response) if response.code == 200
322
330
  exception_class = case response.code
@@ -44,8 +44,9 @@ module Cloudinary::CarrierWave
44
44
  public_id = self.default_public_id
45
45
  return nil if public_id.nil?
46
46
  else
47
- public_id = options.include?(:version) ? self.my_public_id : self.full_public_id
48
- end
47
+ public_id = self.my_public_id
48
+ options[:version] ||= self.stored_version
49
+ end
49
50
  options = self.transformation.merge(options) if self.version_name.present?
50
51
 
51
52
  Cloudinary::Utils.cloudinary_url(public_id, {:format=>self.format, :resource_type=>self.resource_type, :type=>self.storage_type}.merge(options))
@@ -0,0 +1,55 @@
1
+ class Cloudinary::Search
2
+ def initialize
3
+ @query_hash = {
4
+ :sort_by => [],
5
+ :aggregate => [],
6
+ :with_field => []
7
+ }
8
+ end
9
+
10
+ ## implicitly generate an instance delegate the method
11
+ def self.method_missing(method_name, *arguments)
12
+ instance = new
13
+ instance.send(method_name, *arguments)
14
+ end
15
+
16
+ def expression(value)
17
+ @query_hash[:expression] = value
18
+ self
19
+ end
20
+
21
+ def max_results(value)
22
+ @query_hash[:max_results] = value
23
+ self
24
+ end
25
+
26
+ def next_cursor(value)
27
+ @query_hash[:next_cursor] = value
28
+ self
29
+ end
30
+
31
+ def sort_by(field_name, dir = 'desc')
32
+ @query_hash[:sort_by].push(field_name => dir)
33
+ self
34
+ end
35
+
36
+ def aggregate(value)
37
+ @query_hash[:aggregate].push(value)
38
+ self
39
+ end
40
+
41
+ def with_field(value)
42
+ @query_hash[:with_field].push(value)
43
+ self
44
+ end
45
+
46
+ def to_h
47
+ @query_hash.select { |_, value| !value.nil? && !(value.is_a?(Array) && value.empty?) }
48
+ end
49
+
50
+ def execute(options = {})
51
+ options[:content_type] = :json
52
+ uri = 'resources/search'
53
+ Cloudinary::Api.call_api(:post, uri, to_h, options)
54
+ end
55
+ end
@@ -3,126 +3,236 @@ require 'time'
3
3
  require 'set'
4
4
  class Cloudinary::Static
5
5
  IGNORE_FILES = [".svn", "CVS", "RCS", ".git", ".hg"]
6
- SUPPORTED_IMAGES = [/\.(gif|jpe?g|png|bmp|ico|webp|wdp|jxr|jp2|svg|pdf)$/i]
7
- STATIC_IMAGE_DIRS = ["app/assets/images", "lib/assets/images", "vendor/assets/images", "public/images"]
6
+ DEFAULT_IMAGE_DIRS = ["app/assets/images", "lib/assets/images", "vendor/assets/images", "public/images"]
7
+ DEFAULT_IMAGE_EXTENSION_MASK = 'gif|jpe?g|png|bmp|ico|webp|wdp|jxr|jp2|svg|pdf'
8
8
  METADATA_FILE = ".cloudinary.static"
9
9
  METADATA_TRASH_FILE = ".cloudinary.static.trash"
10
-
11
- def self.discover
12
- ignore_files = Cloudinary.config.ignore_files || IGNORE_FILES
13
- relative_dirs = Cloudinary.config.static_image_dirs || STATIC_IMAGE_DIRS
14
- dirs = relative_dirs.map{|dir| self.root.join(dir)}.select(&:exist?)
15
- dirs.each do
16
- |dir|
17
- dir.find do
18
- |path|
19
- file = path.basename.to_s
20
- if ignore_files.any?{|pattern| pattern.is_a?(String) ? pattern == file : file.match(pattern)}
21
- Find.prune
22
- next
23
- elsif path.directory?
10
+
11
+ class << self
12
+ def sync(options={})
13
+ options = options.clone
14
+ delete_missing = options.delete(:delete_missing)
15
+ found_paths = Set.new
16
+ found_public_paths = {}
17
+ found_public_ids = Set.new
18
+ metadata = build_metadata
19
+ metadata_lines = []
20
+ counts = { :not_changed => 0, :uploaded => 0, :deleted => 0, :not_found => 0}
21
+ discover_all do |path, public_path|
22
+ next if found_paths.include?(path)
23
+ if found_public_paths[public_path]
24
+ print "Warning: duplicate #{public_path} in #{path} - already taken from #{found_public_paths[public_path]}\n"
24
25
  next
25
- elsif SUPPORTED_IMAGES.none?{|pattern| pattern.is_a?(String) ? pattern == file : file.match(pattern)}
26
- next
26
+ end
27
+ found_paths << path
28
+ found_public_paths[public_path] = path
29
+ data = root.join(path).read(:mode=>"rb")
30
+ ext = path.extname
31
+ format = ext[1..-1]
32
+ md5 = Digest::MD5.hexdigest(data)
33
+ public_id = "#{public_path.basename(ext)}-#{md5}"
34
+ found_public_ids << public_id
35
+ item_metadata = metadata.delete(public_path.to_s)
36
+ if item_metadata && item_metadata["public_id"] == public_id # Signature match
37
+ counts[:not_changed] += 1
38
+ print "#{public_path} - #{public_id} - Not changed\n"
39
+ result = item_metadata
27
40
  else
28
- relative_path = path.relative_path_from(self.root)
29
- public_path = path.relative_path_from(dir.dirname)
30
- yield(relative_path, public_path)
41
+ counts[:uploaded] += 1
42
+ print "#{public_path} - #{public_id} - Uploading\n"
43
+ result = Cloudinary::Uploader.upload(Cloudinary::Blob.new(data, :original_filename=>path.to_s),
44
+ options.merge(:format=>format, :public_id=>public_id, :type=>:asset, :resource_type=>resource_type(path.to_s))
45
+ ).merge("upload_time"=>Time.now)
31
46
  end
47
+ metadata_lines << [public_path, public_id, result["upload_time"].to_i, result["version"], result["width"], result["height"]].join("\t")+"\n"
32
48
  end
49
+ File.open(metadata_file_path, "w"){|f| f.print(metadata_lines.join)}
50
+ metadata.to_a.each do |path, info|
51
+ counts[:not_found] += 1
52
+ print "#{path} - #{info["public_id"]} - Not found\n"
53
+ end
54
+ # Files no longer needed
55
+ trash = metadata.to_a + build_metadata(metadata_trash_file_path, false).reject{|public_path, info| found_public_ids.include?(info["public_id"])}
56
+
57
+ if delete_missing
58
+ trash.each do
59
+ |path, info|
60
+ counts[:deleted] += 1
61
+ print "#{path} - #{info["public_id"]} - Deleting\n"
62
+ Cloudinary::Uploader.destroy(info["public_id"], options.merge(:type=>:asset))
63
+ end
64
+ FileUtils.rm_f(metadata_trash_file_path)
65
+ else
66
+ # Add current removed file to the trash file.
67
+ metadata_lines = trash.map do
68
+ |public_path, info|
69
+ [public_path, info["public_id"], info["upload_time"].to_i, info["version"], info["width"], info["height"]].join("\t")+"\n"
70
+ end
71
+ File.open(metadata_trash_file_path, "w"){|f| f.print(metadata_lines.join)}
72
+ end
73
+
74
+ print "\nCompleted syncing static resources to Cloudinary\n"
75
+ print counts.sort.reject{|k,v| v == 0}.map{|k,v| "#{v} #{k.to_s.gsub('_', ' ').capitalize}"}.join(", ") + "\n"
33
76
  end
34
- end
35
77
 
36
- def self.root
37
- Cloudinary.app_root
38
- end
78
+ # ## Cloudinary::Utils support ###
79
+ def public_id_and_resource_type_from_path(path)
80
+ metadata = build_metadata
81
+ path = path.sub(/^\//, '')
82
+ data = metadata[path]
83
+ unless data
84
+ public_prefixes.each do |prefix|
85
+ if metadata[File.join(prefix, path)]
86
+ data = metadata[File.join(prefix, path)]
87
+ break
88
+ end
89
+ end
90
+ end
91
+ return unless data
92
+ [data['public_id'], resource_type(path)]
93
+ end
39
94
 
40
- def self.metadata_file_path
41
- self.root.join(METADATA_FILE)
42
- end
95
+ private
96
+ def root
97
+ Cloudinary.app_root
98
+ end
43
99
 
44
- def self.metadata_trash_file_path
45
- self.root.join(METADATA_TRASH_FILE)
46
- end
47
-
48
- def self.metadata(metadata_file = metadata_file_path, hash=true)
49
- metadata = []
50
- if File.exist?(metadata_file)
51
- IO.foreach(metadata_file) do
100
+ def metadata_file_path
101
+ root.join(METADATA_FILE)
102
+ end
103
+
104
+ def metadata_trash_file_path
105
+ root.join(METADATA_TRASH_FILE)
106
+ end
107
+
108
+ def build_metadata(metadata_file = metadata_file_path, hash = true)
109
+ metadata = []
110
+ if File.exist?(metadata_file)
111
+ IO.foreach(metadata_file) do
52
112
  |line|
53
- line.strip!
54
- next if line.blank?
55
- path, public_id, upload_time, version, width, height = line.split("\t")
56
- metadata << [path, {
57
- "public_id" => public_id,
58
- "upload_time" => Time.at(upload_time.to_i).getutc,
59
- "version" => version,
60
- "width" => width.to_i,
61
- "height" => height.to_i
62
- }]
113
+ line.strip!
114
+ next if line.blank?
115
+ path, public_id, upload_time, version, width, height = line.split("\t")
116
+ metadata << [path, {
117
+ "public_id" => public_id,
118
+ "upload_time" => Time.at(upload_time.to_i).getutc,
119
+ "version" => version,
120
+ "width" => width.to_i,
121
+ "height" => height.to_i
122
+ }]
123
+ end
63
124
  end
125
+ hash ? Hash[*metadata.flatten] : metadata
64
126
  end
65
- hash ? Hash[*metadata.flatten] : metadata
66
- end
67
127
 
68
- def self.sync(options={})
69
- options = options.clone
70
- delete_missing = options.delete(:delete_missing)
71
- metadata = self.metadata
72
- found_paths = Set.new
73
- found_public_ids = Set.new
74
- metadata_lines = []
75
- counts = { :not_changed => 0, :uploaded => 0, :deleted => 0, :not_found => 0}
76
- self.discover do
77
- |path, public_path|
78
- next if found_paths.include?(path)
79
- found_paths << path
80
- data = self.root.join(path).read(:mode=>"rb")
81
- ext = path.extname
82
- format = ext[1..-1]
83
- md5 = Digest::MD5.hexdigest(data)
84
- public_id = "#{public_path.basename(ext)}-#{md5}"
85
- found_public_ids << public_id
86
- current_metadata = metadata.delete(public_path.to_s)
87
- if current_metadata && current_metadata["public_id"] == public_id # Signature match
88
- counts[:not_changed] += 1
89
- $stderr.print "#{public_path} - #{public_id} - Not changed\n"
90
- result = current_metadata
91
- else
92
- counts[:uploaded] += 1
93
- $stderr.print "#{public_path} - #{public_id} - Uploading\n"
94
- result = Cloudinary::Uploader.upload(Cloudinary::Blob.new(data, :original_filename=>path.to_s),
95
- options.merge(:format=>format, :public_id=>public_id, :type=>:asset)
96
- ).merge("upload_time"=>Time.now)
128
+ def discover_all(&block)
129
+ static_file_config.each do |group, data|
130
+ print "-> Syncing #{group}...\n"
131
+ discover(absolutize(data['dirs']), extension_matcher_for(group), &block)
132
+ print "=========================\n"
133
+ end
134
+ end
135
+
136
+ def discover(dirs, matcher)
137
+ return unless matcher
138
+
139
+ dirs.each do |dir|
140
+ print "Scanning #{dir.relative_path_from(root)}...\n"
141
+ dir.find do |path|
142
+ file = path.basename.to_s
143
+ if ignore_file?(file)
144
+ Find.prune
145
+ next
146
+ elsif path.directory? || !matcher.call(path.to_s)
147
+ next
148
+ else
149
+ relative_path = path.relative_path_from(root)
150
+ public_path = path.relative_path_from(dir.dirname)
151
+ yield(relative_path, public_path)
152
+ end
153
+ end
97
154
  end
98
- metadata_lines << [public_path, public_id, result["upload_time"].to_i, result["version"], result["width"], result["height"]].join("\t")+"\n"
99
155
  end
100
- File.open(self.metadata_file_path, "w"){|f| f.print(metadata_lines.join)}
101
- metadata.to_a.each do |path, info|
102
- counts[:not_found] += 1
103
- $stderr.print "#{path} - #{info["public_id"]} - Not found\n"
156
+
157
+ def ignore_file?(file)
158
+ matches?(file, Cloudinary.config.ignore_files || IGNORE_FILES)
104
159
  end
105
- # Files no longer needed
106
- trash = metadata.to_a + self.metadata(metadata_trash_file_path, false).reject{|public_path, info| found_public_ids.include?(info["public_id"])}
107
-
108
- if delete_missing
109
- trash.each do
110
- |path, info|
111
- counts[:deleted] += 1
112
- $stderr.print "#{path} - #{info["public_id"]} - Deleting\n"
113
- Cloudinary::Uploader.destroy(info["public_id"], options.merge(:type=>:asset))
160
+
161
+ # Test for matching either strings or regexps
162
+ def matches?(target, patterns)
163
+ Array(patterns).any? {|pattern| pattern.is_a?(String) ? pattern == target : target.match(pattern)}
164
+ end
165
+
166
+ def extension_matcher_for(group)
167
+ group = group.to_s
168
+ return unless static_file_config[group]
169
+ @matchers = {}
170
+ @matchers[group] ||= ->(target) do
171
+ !!target.match(extension_mask_to_regex(static_file_config[group]['file_mask']))
114
172
  end
115
- FileUtils.rm_f(self.metadata_trash_file_path)
116
- else
117
- # Add current removed file to the trash file.
118
- metadata_lines = trash.map do
119
- |public_path, info|
120
- [public_path, info["public_id"], info["upload_time"].to_i, info["version"], info["width"], info["height"]].join("\t")+"\n"
173
+ end
174
+
175
+ def static_file_config
176
+ @static_file_config ||= begin
177
+ config = Cloudinary.config.static_files || {}
178
+
179
+ # Default
180
+ config['images'] ||= {}
181
+ config['images']['dirs'] ||= Cloudinary.config.static_image_dirs # Backwards compatibility
182
+ config['images']['dirs'] ||= DEFAULT_IMAGE_DIRS
183
+ config['images']['file_mask'] ||= DEFAULT_IMAGE_EXTENSION_MASK
184
+ # Validate
185
+ config.each do |group, data|
186
+ unless data && data['dirs'] && data['file_mask']
187
+ print "In config, static_files group '#{group}' needs to have both 'dirs' and 'file_mask' defined.\n"
188
+ exit
189
+ end
190
+ end
191
+
192
+ config
121
193
  end
122
- File.open(self.metadata_trash_file_path, "w"){|f| f.print(metadata_lines.join)}
194
+ end
195
+
196
+ def reset_static_file_config!
197
+ @static_file_config = nil
198
+ end
199
+
200
+ def image?(path)
201
+ extension_matcher_for(:images).call(path)
202
+ end
203
+
204
+ def resource_type(path)
205
+ if image?(path)
206
+ :image
207
+ else
208
+ :raw
209
+ end
210
+ end
211
+
212
+ def extension_mask_to_regex(extension_mask)
213
+ extension_mask && /\.(?:#{extension_mask})$/i
214
+ end
215
+
216
+ def public_prefixes
217
+ @public_prefixes ||= static_file_config.reduce([]) do |result, (group, data)|
218
+ result << data['dirs'].map { |dir| Pathname.new(dir).basename.to_s }
219
+ end.flatten.uniq
220
+ end
221
+
222
+ def absolutize(dirs)
223
+ dirs.map do |relative_dir|
224
+ absolute_dir = root.join(relative_dir)
225
+ if absolute_dir.exist?
226
+ absolute_dir
227
+ else
228
+ print "Skipping #{relative_dir} (does not exist)\n"
229
+ nil
230
+ end
231
+ end.compact
123
232
  end
124
233
 
125
- $stderr.print "\nCompleted syncing static resources to Cloudinary\n"
126
- $stderr.print counts.sort.reject{|k,v| v == 0}.map{|k,v| "#{v} #{k.to_s.gsub('_', ' ').capitalize}"}.join(", ") + "\n"
234
+ def print(s)
235
+ $stderr.print(s)
236
+ end
127
237
  end
128
238
  end
@@ -376,21 +376,20 @@ class Cloudinary::Utils
376
376
  source = source.to_s
377
377
  if !force_remote
378
378
  return original_source if (type.nil? || type == "asset") && source.match(%r(^https?:/)i)
379
- if source.start_with?("/")
379
+ if type == "asset"
380
+ # config.static_image_support left for backwards compatibility
381
+ if (Cloudinary.config.static_file_support || Cloudinary.config.static_image_support) && defined?(Cloudinary::Static)
382
+ source, resource_type = Cloudinary::Static.public_id_and_resource_type_from_path(source)
383
+ end
384
+ return original_source unless source
385
+ source += File.extname(original_source) if !format
386
+ elsif source.start_with?("/")
380
387
  if source.start_with?("/images/")
381
388
  source = source.sub(%r(/images/), '')
382
389
  else
383
390
  return original_source
384
391
  end
385
392
  end
386
- @metadata ||= defined?(Cloudinary::Static) ? Cloudinary::Static.metadata : {}
387
- if type == "asset" && @metadata["images/#{source}"]
388
- return original_source if !Cloudinary.config.static_image_support
389
- source = @metadata["images/#{source}"]["public_id"]
390
- source += File.extname(original_source) if !format
391
- elsif type == "asset"
392
- return original_source # requested asset, but no metadata - probably local file. return.
393
- end
394
393
  end
395
394
 
396
395
  resource_type, type = finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
@@ -1,4 +1,4 @@
1
1
  # Copyright Cloudinary
2
2
  module Cloudinary
3
- VERSION = "1.7.0"
3
+ VERSION = "1.8.0"
4
4
  end
@@ -1,7 +1,9 @@
1
- namespace :cloudinary do
2
- desc "Sync static resources with cloudinary"
3
- task :sync_static => :environment do
4
- delete_missing = ENV["DELETE_MISSING"] == 'true' || ENV["DELETE_MISSING"] == '1'
5
- Cloudinary::Static.sync(:delete_missing=>delete_missing)
1
+ unless Rake::Task.task_defined?('cloudinary:sync_static') # prevent double-loading/execution
2
+ namespace :cloudinary do
3
+ desc "Sync static resources with cloudinary"
4
+ task :sync_static do
5
+ delete_missing = ENV['DELETE_MISSING'] == 'true' || ENV['DELETE_MISSING'] == '1'
6
+ Cloudinary::Static.sync(:delete_missing => delete_missing)
7
+ end
6
8
  end
7
- end
9
+ end
@@ -10,14 +10,15 @@ describe Cloudinary::Api do
10
10
  test_id_1 = "#{prefix}_1"
11
11
  test_id_2 = "#{prefix}_2"
12
12
  test_id_3 = "#{prefix}_3"
13
+ test_key = "test_key_#{SUFFIX}"
13
14
  before(:all) do
14
15
 
15
16
  @api = Cloudinary::Api
16
17
  Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_1, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "key=value", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
17
18
  Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_2, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "key=value", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
18
19
  Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_3, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "key=value", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
19
- Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_1, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "test-key=test", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
20
- Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_3, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "test-key=tasty", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
20
+ Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_1, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "#{test_key}=test", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
21
+ Cloudinary::Uploader.upload(TEST_IMG, :public_id => test_id_3, :tags => [TEST_TAG, TIMESTAMP_TAG], :context => "#{test_key}=tasty", :eager =>[:width =>TEST_WIDTH, :crop =>:scale])
21
22
  end
22
23
 
23
24
  after(:all) do
@@ -79,9 +80,9 @@ describe Cloudinary::Api do
79
80
  end
80
81
 
81
82
  it "should allow listing resources by context" do
82
- resources = @api.resources_by_context('test-key')["resources"]
83
+ resources = @api.resources_by_context(test_key)["resources"]
83
84
  expect(resources.count).to eq(2)
84
- resources = @api.resources_by_context('test-key','test')["resources"]
85
+ resources = @api.resources_by_context(test_key,'test')["resources"]
85
86
  expect(resources.count).to eq(1)
86
87
  end
87
88
 
@@ -0,0 +1 @@
1
+ coffeescript = true
@@ -0,0 +1 @@
1
+ { alert("I am javascript!");}
@@ -0,0 +1,3 @@
1
+ p#css {
2
+ color: red;
3
+ }
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+ require 'cloudinary'
3
+
4
+ require 'tmpdir'
5
+ require 'pathname'
6
+
7
+ describe 'cloudinary:sync_static' do
8
+ include_context 'rake' # context needs to have the exact rake task as name
9
+ include Helpers::TempFileHelpers
10
+
11
+ before(:all) do
12
+ copy_root_to_temp('spec/data/sync_static/')
13
+
14
+ # Reuse some existing spec assets so as not to add weight to the project
15
+ copy_file_to_temp('spec/logo.png', 'app/assets/images/logo1.png')
16
+ copy_file_to_temp('spec/logo.png', 'app/assets/images/logo2.png')
17
+ copy_file_to_temp('samples/basic/lake.jpg', 'app/assets/images/lake1.jpg')
18
+ copy_file_to_temp('samples/basic/lake.jpg', 'public/images/lake2.jpg')
19
+ end
20
+
21
+ after(:all) do
22
+ clean_up_temp_files!
23
+ end
24
+
25
+ before (:each) do
26
+ allow(Cloudinary).to receive(:app_root).and_return(Pathname.new(temp_root))
27
+ Cloudinary::Static.send(:reset_static_file_config!)
28
+ end
29
+
30
+ after (:each) do
31
+ # destroy all uploaded assets
32
+ Cloudinary::Static.send(:build_metadata).each do |_, data|
33
+ Cloudinary::Uploader.destroy(data['public_id'], {:type => :asset})
34
+ end
35
+ # delete metadata_file_path
36
+ FileUtils.rm_f(Cloudinary::Static.send(:metadata_file_path))
37
+ FileUtils.rm_f(Cloudinary::Static.send(:metadata_trash_file_path))
38
+ end
39
+
40
+ it 'should find correct file when running in default configuration' do
41
+ # with default settings, 4 images only will be uploaded
42
+ subject.invoke
43
+ expect(Cloudinary::Static.send(:build_metadata).size).to eq 4
44
+ end
45
+
46
+ it 'should respect deprecated static_image_dirs config setting' do
47
+ allow(Cloudinary.config).to receive(:static_image_dirs).and_return(['public/images'])
48
+ subject.invoke
49
+ expect(Cloudinary::Static.send(:build_metadata).size).to eq 1
50
+ end
51
+
52
+ it 'should allow to specify only dirs in images group, taking the file_mask from default' do
53
+ allow(Cloudinary.config).to receive(:static_files).and_return({
54
+ 'images' => {
55
+ 'dirs' => ['public/images']
56
+ }
57
+ })
58
+ subject.invoke
59
+ expect(Cloudinary::Static.send(:build_metadata).size).to eq 1
60
+ end
61
+
62
+ it 'should allow to specify only file_mask in images group, taking the dirs from default' do
63
+ allow(Cloudinary.config).to receive(:static_files).and_return({
64
+ 'images' => {
65
+ 'file_mask' => 'png'
66
+ }
67
+ })
68
+ subject.invoke
69
+ expect(Cloudinary::Static.send(:build_metadata).size).to eq 2
70
+ end
71
+
72
+ it 'should allow to specify regex expressions in file_mask' do
73
+ allow(Cloudinary.config).to receive(:static_files).and_return({
74
+ 'images' => {
75
+ 'file_mask' => 'p.?g|jp.?'
76
+ },
77
+ 'javascripts' => {
78
+ 'dirs' => ['app/assets/javascripts'],
79
+ 'file_mask' => 'js'
80
+ },
81
+ 'stylesheets' => {
82
+ 'dirs' => ['app/assets/stylesheets'],
83
+ 'file_mask' => 'css'
84
+ }
85
+ })
86
+ subject.invoke
87
+ expect(Cloudinary::Static.send(:build_metadata).size).to eq 6
88
+ end
89
+
90
+ context 'Cloudinary::Utils.cloudinary_url' do
91
+ def all_asset_forms_of(public_id)
92
+ [public_id, "/#{public_id}", public_id.split('/').last]
93
+ end
94
+
95
+ RSpec::Matchers.define :be_asset_mapped_by_cloudinary_url_to do |expected|
96
+ match do |actual|
97
+ actual.all? do |public_path|
98
+ Cloudinary::Utils.cloudinary_url(public_path, :cloud_name => 'test', :type => 'asset') == expected
99
+ end
100
+ end
101
+ end
102
+
103
+ before(:each) do
104
+ allow(Cloudinary.config).to receive(:static_files).and_return({
105
+ 'images' => {
106
+ 'dirs' => ['app/assets/images'],
107
+ 'file_mask' => 'p.?g|jp.?'
108
+ },
109
+ 'javascripts' => {
110
+ 'dirs' => ['app/assets/javascripts'],
111
+ 'file_mask' => 'js'
112
+ },
113
+ 'stylesheets' => {
114
+ 'dirs' => ['app/assets/stylesheets'],
115
+ 'file_mask' => 'css'
116
+ }
117
+ })
118
+
119
+ end
120
+
121
+ it 'should return Cloudinary asset urls for assets when Cloudinary.config.static_file_support is true' do
122
+ allow(Cloudinary.config).to receive(:static_file_support).and_return(true)
123
+ subject.invoke
124
+
125
+ expect(all_asset_forms_of('images/logo1.png')).to be_asset_mapped_by_cloudinary_url_to('http://res.cloudinary.com/test/image/asset/logo1-7dc60722d4653261648038b579fdb89e.png')
126
+ expect(all_asset_forms_of('javascripts/1.js')).to be_asset_mapped_by_cloudinary_url_to('http://res.cloudinary.com/test/raw/asset/1-b01de57adb485efdde843154d030644e.js')
127
+ expect(all_asset_forms_of('stylesheets/1.css')).to be_asset_mapped_by_cloudinary_url_to('http://res.cloudinary.com/test/raw/asset/1-f24cc6123afd401ab86d8596cabc619f.css')
128
+
129
+ # without :type => 'asset'
130
+ expect(Cloudinary::Utils.cloudinary_url('logo1.png')).not_to include('7dc60722d4653261648038b579fdb89e')
131
+ end
132
+
133
+ it 'should return Cloudinary asset urls for assets when Cloudinary.config.static_image_support is true (backwards compatibility)' do
134
+ allow(Cloudinary.config).to receive(:static_image_support).and_return(true)
135
+ subject.invoke
136
+
137
+ expect(all_asset_forms_of('images/logo1.png')).to be_asset_mapped_by_cloudinary_url_to('http://res.cloudinary.com/test/image/asset/logo1-7dc60722d4653261648038b579fdb89e.png')
138
+ end
139
+ end
140
+
141
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+ require 'cloudinary'
3
+
4
+ describe Cloudinary::Search do
5
+ context 'unit' do
6
+ it 'should create empty json' do
7
+ query_hash = Cloudinary::Search.to_h
8
+ expect(query_hash).to eq({})
9
+ end
10
+
11
+ it 'should always return same object in fluent interface' do
12
+ instance = Cloudinary::Search.new
13
+ %w(expression sort_by max_results next_cursor aggregate with_field).each do |method|
14
+ same_instance = instance.send(method, 'emptyarg')
15
+ expect(instance).to eq(same_instance)
16
+ end
17
+ end
18
+
19
+ it 'should add expression to query' do
20
+ query = Cloudinary::Search.expression('format:jpg').to_h
21
+ expect(query).to eq(expression: 'format:jpg')
22
+ end
23
+
24
+ it 'should add sort_by to query' do
25
+ query = Cloudinary::Search.sort_by('created_at', 'asc').sort_by('updated_at', 'desc').to_h
26
+ expect(query).to eq(sort_by: [{ 'created_at' => 'asc' }, { 'updated_at' => 'desc' }])
27
+ end
28
+
29
+ it 'should add max_results to query' do
30
+ query = Cloudinary::Search.max_results('format:jpg').to_h
31
+ expect(query).to eq(max_results: 'format:jpg')
32
+ end
33
+
34
+ it 'should add next_cursor to query' do
35
+ query = Cloudinary::Search.next_cursor('format:jpg').to_h
36
+ expect(query).to eq(next_cursor: 'format:jpg')
37
+ end
38
+
39
+ it 'should add aggregations arguments as array to query' do
40
+ query = Cloudinary::Search.aggregate('format').aggregate('size_category').to_h
41
+ expect(query).to eq(aggregate: %w(format size_category))
42
+ end
43
+
44
+ it 'should add with_field to query' do
45
+ query = Cloudinary::Search.with_field('context').with_field('tags').to_h
46
+ expect(query).to eq(with_field: %w(context tags))
47
+ end
48
+ end
49
+
50
+ context 'integration' do
51
+ SEARCH_TAG = TIMESTAMP_TAG + "_search"
52
+ include_context 'cleanup', SEARCH_TAG
53
+ prefix = "api_test_#{SUFFIX}"
54
+ test_id_1 = "#{prefix}_1"
55
+ test_id_2 = "#{prefix}_2"
56
+ test_id_3 = "#{prefix}_3"
57
+ before(:all) do
58
+ Cloudinary::Uploader.upload(TEST_IMG, public_id: test_id_1, tags: [TEST_TAG, TIMESTAMP_TAG, SEARCH_TAG], context: 'stage=in_review')
59
+ Cloudinary::Uploader.upload(TEST_IMG, public_id: test_id_2, tags: [TEST_TAG, TIMESTAMP_TAG, SEARCH_TAG], context: 'stage=new')
60
+ Cloudinary::Uploader.upload(TEST_IMG, public_id: test_id_3, tags: [TEST_TAG, TIMESTAMP_TAG, SEARCH_TAG], context: 'stage=validated')
61
+ sleep(3)
62
+ end
63
+
64
+ it "should return all images tagged with #{SEARCH_TAG}" do
65
+ results = Cloudinary::Search.expression("tags:#{SEARCH_TAG}").execute
66
+ expect(results['resources'].count).to eq 3
67
+ end
68
+
69
+ it "should return resource #{test_id_1}" do
70
+ results = Cloudinary::Search.expression("public_id:#{test_id_1}").execute
71
+ expect(results['resources'].count).to eq 1
72
+ end
73
+
74
+ it 'should paginate resources limited by tag and ordered by ascending public_id' do
75
+ results = Cloudinary::Search.max_results(1).expression("tags:#{SEARCH_TAG}").sort_by('public_id', 'asc').execute
76
+ expect(results['resources'].count).to eq 1
77
+ expect(results['resources'][0]['public_id']).to eq test_id_1
78
+ expect(results['total_count']).to eq 3
79
+
80
+ results = Cloudinary::Search.max_results(1).expression("tags:#{SEARCH_TAG}").sort_by('public_id', 'asc').next_cursor(results['next_cursor']).execute
81
+ expect(results['resources'].count).to eq 1
82
+ expect(results['resources'][0]['public_id']).to eq test_id_2
83
+ expect(results['total_count']).to eq 3
84
+
85
+ results = Cloudinary::Search.max_results(1).expression("tags:#{SEARCH_TAG}").sort_by('public_id', 'asc').next_cursor(results['next_cursor']).execute
86
+ expect(results['resources'].count).to eq 1
87
+ expect(results['resources'][0]['public_id']).to eq test_id_3
88
+ expect(results['total_count']).to eq 3
89
+ expect(results['next_cursor']).to be_nil
90
+ end
91
+
92
+ it 'should include context' do
93
+ results = Cloudinary::Search.expression("tags:#{SEARCH_TAG}").with_field('context').execute
94
+ expect(results['resources'].count).to eq 3
95
+ results['resources'].each do |res|
96
+ expect(res['context'].keys).to eq ['stage']
97
+ end
98
+ end
99
+ it 'should include context, tags and image_metadata' do
100
+ results = Cloudinary::Search.expression("tags:#{SEARCH_TAG}").with_field('context').with_field('tags').with_field('image_metadata').execute
101
+ expect(results['resources'].count).to eq 3
102
+ results['resources'].each do |res|
103
+ expect(res['context'].keys).to eq ['stage']
104
+ expect(res.key?('image_metadata')).to eq true
105
+ expect(res['tags'].count).to eq 3
106
+ end
107
+ end
108
+ end
109
+ end
@@ -16,6 +16,14 @@ KEY = "00112233FF99"
16
16
  ALT_KEY = "CCBB2233FF00"
17
17
 
18
18
 
19
+ Dir[File.join(File.dirname(__FILE__), '/support/**/*.rb')].each {|f| require f}
20
+
21
+ module RSpec
22
+ def self.project_root
23
+ File.join(File.dirname(__FILE__), '..')
24
+ end
25
+ end
26
+
19
27
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
20
28
  RSpec.configure do |config|
21
29
  unless RSpec::Version::STRING.match( /^3/)
@@ -136,7 +144,13 @@ end
136
144
  RSpec::Matchers.define :be_served_by_cloudinary do
137
145
  match do |url|
138
146
  if url.is_a? Array
139
- url = Cloudinary::Utils.cloudinary_url( url[0], url[1])
147
+ url, options = url
148
+ url = Cloudinary::Utils.cloudinary_url(url, options.clone)
149
+ if Cloudinary.config.upload_prefix
150
+ res_prefix_uri = URI.parse(Cloudinary.config.upload_prefix)
151
+ res_prefix_uri.path = '/res'
152
+ url.gsub!(/https?:\/\/res.cloudinary.com/, res_prefix_uri.to_s)
153
+ end
140
154
  end
141
155
  code = 0
142
156
  @url = url
@@ -0,0 +1,22 @@
1
+ module Helpers
2
+ module TempFileHelpers
3
+ def clean_up_temp_files!
4
+ FileUtils.remove_entry temp_root
5
+ end
6
+
7
+ def temp_root
8
+ @temp_root ||= Dir.mktmpdir 'test_root'
9
+ end
10
+
11
+ def copy_root_to_temp(source)
12
+ source = File.join(RSpec.project_root, source) unless Pathname.new(source).directory?
13
+ FileUtils.copy_entry source, temp_root
14
+ end
15
+
16
+ def copy_file_to_temp(source, dest)
17
+ dest_path = File.join(temp_root, dest)
18
+ FileUtils.mkdir_p(File.dirname(dest_path))
19
+ FileUtils.copy_entry(File.join(RSpec.project_root, source), dest_path)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ require "rake"
2
+
3
+ # From https://robots.thoughtbot.com/test-rake-tasks-like-a-boss
4
+ shared_context "rake" do
5
+ let(:rake) { Rake::Application.new }
6
+ let(:task_name) { self.class.top_level_description }
7
+ let(:task_path) { "lib/tasks/#{task_name.split(":").first}" }
8
+ subject { rake[task_name] }
9
+
10
+ def loaded_files_excluding_current_rake_file
11
+ $".reject {|file| file == Pathname.new(RSpec.project_root).join("#{task_path}.rake").to_s }
12
+ end
13
+
14
+ before do
15
+ Rake.application = rake
16
+ Rake.application.rake_require(task_path, [RSpec.project_root], loaded_files_excluding_current_rake_file)
17
+ Rake::Task.define_task(:environment)
18
+ end
19
+ end
@@ -15,5 +15,4 @@ describe Utils do
15
15
  it "should parse a percent range value" do
16
16
  expect(Utils.instance_eval { norm_range_value("20p") }).to eq("20p")
17
17
  end
18
-
19
18
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudinary
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nadav Soferman
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2017-04-09 00:00:00.000000000 Z
13
+ date: 2017-04-30 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: aws_cf_signer
@@ -54,6 +54,20 @@ dependencies:
54
54
  - - ">="
55
55
  - !ruby/object:Gem::Version
56
56
  version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: rake
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
57
71
  - !ruby/object:Gem::Dependency
58
72
  name: rest-client
59
73
  requirement: !ruby/object:Gem::Requirement
@@ -148,6 +162,7 @@ files:
148
162
  - lib/cloudinary/ostruct2.rb
149
163
  - lib/cloudinary/preloaded_file.rb
150
164
  - lib/cloudinary/railtie.rb
165
+ - lib/cloudinary/search.rb
151
166
  - lib/cloudinary/static.rb
152
167
  - lib/cloudinary/uploader.rb
153
168
  - lib/cloudinary/utils.rb
@@ -159,11 +174,18 @@ files:
159
174
  - spec/auth_token_spec.rb
160
175
  - spec/cloudinary_helper_spec.rb
161
176
  - spec/cloudinary_spec.rb
177
+ - spec/data/sync_static/app/assets/javascripts/1.coffee
178
+ - spec/data/sync_static/app/assets/javascripts/1.js
179
+ - spec/data/sync_static/app/assets/stylesheets/1.css
162
180
  - spec/docx.docx
163
181
  - spec/favicon.ico
164
182
  - spec/logo.png
183
+ - spec/rake_spec.rb
184
+ - spec/search_spec.rb
165
185
  - spec/spec_helper.rb
166
186
  - spec/streaminig_profiles_api_spec.rb
187
+ - spec/support/helpers/temp_file_helpers.rb
188
+ - spec/support/shared_contexts/rake.rb
167
189
  - spec/uploader_spec.rb
168
190
  - spec/utils_methods_spec.rb
169
191
  - spec/utils_spec.rb
@@ -211,11 +233,18 @@ test_files:
211
233
  - spec/auth_token_spec.rb
212
234
  - spec/cloudinary_helper_spec.rb
213
235
  - spec/cloudinary_spec.rb
236
+ - spec/data/sync_static/app/assets/javascripts/1.coffee
237
+ - spec/data/sync_static/app/assets/javascripts/1.js
238
+ - spec/data/sync_static/app/assets/stylesheets/1.css
214
239
  - spec/docx.docx
215
240
  - spec/favicon.ico
216
241
  - spec/logo.png
242
+ - spec/rake_spec.rb
243
+ - spec/search_spec.rb
217
244
  - spec/spec_helper.rb
218
245
  - spec/streaminig_profiles_api_spec.rb
246
+ - spec/support/helpers/temp_file_helpers.rb
247
+ - spec/support/shared_contexts/rake.rb
219
248
  - spec/uploader_spec.rb
220
249
  - spec/utils_methods_spec.rb
221
250
  - spec/utils_spec.rb