cloudinary 1.0.15 → 1.0.16

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -10,7 +10,7 @@ To install the Cloudinary Ruby GEM, run:
10
10
 
11
11
  $ gem install cloudinary
12
12
 
13
- I you use Rails 3.x or higher, edit your Gemfile, add the following line and run 'bundle'
13
+ If you use Rails 3.x or higher, edit your Gemfile, add the following line and run 'bundle'
14
14
 
15
15
  $ gem 'cloudinary'
16
16
 
@@ -18,17 +18,21 @@ Or in Rails 2.x, edit your environment.rb and add:
18
18
 
19
19
  $ config.gem 'cloudinary'
20
20
 
21
- If you would like to use our optional integration module of image uploads with ActiveRecord using CarrierWave, install CarrierWave to:
21
+ If you would like to use our optional integration module of image uploads with ActiveRecord using CarrierWave, install CarrierWave GEM:
22
22
 
23
23
  $ gem install carrierwave
24
24
 
25
25
  Rails 3.x Gemfile:
26
26
 
27
27
  $ gem 'carrierwave'
28
+ $ gem 'cloudinary'
28
29
 
29
30
  Rails 2.x environment.rb
30
31
 
31
32
  $ config.gem 'carrierwave', :version => '~> 0.4.1'
33
+ $ config.gem 'cloudinary'
34
+
35
+ Note: The CarrierWave GEM should be loaded before the Cloudinary GEM.
32
36
 
33
37
 
34
38
  ## Usage ######################################################################
@@ -1,319 +1,123 @@
1
1
  # Copyright Cloudinary
2
- require 'pp'
3
- module Cloudinary::CarrierWave
4
- CLOUDINARY_PATH = /^([^\/]+)\/upload\/v(\d+)\/([^\/]+)#([^\/]+)$/
5
-
6
- class UploadError < StandardError
7
- attr_reader :http_code
8
- def initialize(message, http_code)
9
- super(message)
10
- @http_code = http_code
11
- end
12
- end
13
-
14
- module ClassMethods
15
- def eager
16
- process :eager => true
17
- end
18
-
19
- def convert(format)
20
- process :convert => format
21
- end
22
-
23
- def resize_to_limit(width, height)
24
- process :resize_to_limit => [width, height]
25
- end
26
-
27
- def resize_to_fit(width, height)
28
- process :resize_to_fit => [width, height]
29
- end
30
-
31
- def resize_to_fill(width, height, gravity="Center")
32
- process :resize_to_fill => [width, height, gravity]
33
- end
2
+ require 'cloudinary/carrier_wave/process'
3
+ require 'cloudinary/carrier_wave/error'
4
+ require 'cloudinary/carrier_wave/remote'
5
+ require 'cloudinary/carrier_wave/preloaded'
6
+ require 'cloudinary/carrier_wave/storage'
34
7
 
35
- def resize_and_pad(width, height, background=:transparent, gravity="Center")
36
- process :resize_and_pad => [width, height, background, gravity]
37
- end
38
-
39
- def scale(width, height)
40
- process :scale => [width, height]
41
- end
42
-
43
- def crop(width, height, gravity="Center")
44
- process :crop => [width, height, gravity]
45
- end
46
-
47
- def cloudinary_transformation(options)
48
- process :cloudinary_transformation => options
49
- end
50
-
51
- def tags(*tags)
52
- process :tags=>tags
53
- end
54
- end
8
+ module Cloudinary::CarrierWave
55
9
 
56
10
  def self.included(base)
57
11
  base.storage Cloudinary::CarrierWave::Storage
58
12
  base.extend ClassMethods
59
13
  base.send(:attr_accessor, :metadata)
60
- base.send(:alias_method, :original_cache!, :cache!)
61
- base.send(:alias_method, :original_cache_name, :cache_name)
62
- base.send(:alias_method, :original_retrieve_from_cache!, :retrieve_from_cache!)
63
- base.send(:include, Override)
64
- end
65
-
66
- module Override
67
- def cache!(new_file)
68
- if new_file.is_a?(String) && new_file.match(CLOUDINARY_PATH)
69
- @file = CloudinaryFile.new(new_file)
70
- self.original_filename = @cache_id = @my_filename = @filename = @file.original_filename
71
- else
72
- original_cache!(new_file)
73
- end
74
- end
75
-
76
- def retrieve_from_cache!(new_file)
77
- if new_file.is_a?(String) && new_file.match(CLOUDINARY_PATH)
78
- @file = CloudinaryFile.new(new_file)
79
- self.original_filename = @cache_id = @my_filename = @filename = @file.original_filename
80
- else
81
- original_retrieve_from_cache!(new_file)
82
- end
83
- end
84
-
85
- def cache_name
86
- if @file.is_a?(CloudinaryFile)
87
- return @file.to_s
88
- else
89
- return original_cache_name
90
- end
91
- end
92
- end
93
-
94
- def set_or_yell(hash, attr, value)
95
- raise "conflicting transformation on #{attr} #{value}!=#{hash[attr]}" if hash[attr]
96
- hash[attr] = value
97
- end
98
-
99
- def transformation
100
- return @transformation if @transformation
101
- transformation = {}
102
- self.class.processors.each do
103
- |name, args|
104
- case name
105
- when :convert # Do nothing. This is handled by format
106
- when :resize_to_limit
107
- set_or_yell(transformation, :width, args[0])
108
- set_or_yell(transformation, :height, args[1])
109
- set_or_yell(transformation, :crop, :limit)
110
- when :resize_to_fit
111
- set_or_yell(transformation, :width, args[0])
112
- set_or_yell(transformation, :height, args[1])
113
- set_or_yell(transformation, :crop, :fit)
114
- when :resize_to_fill
115
- set_or_yell(transformation, :width, args[0])
116
- set_or_yell(transformation, :height, args[1])
117
- set_or_yell(transformation, :gravity, args[2].to_s.downcase)
118
- set_or_yell(transformation, :crop, :fill)
119
- when :resize_and_pad
120
- set_or_yell(transformation, :width, args[0])
121
- set_or_yell(transformation, :height, args[1])
122
- set_or_yell(transformation, :gravity, args[3].to_s.downcase)
123
- set_or_yell(transformation, :crop, :pad)
124
- when :scale
125
- set_or_yell(transformation, :width, args[0])
126
- set_or_yell(transformation, :height, args[1])
127
- set_or_yell(transformation, :crop, :scale)
128
- when :crop
129
- set_or_yell(transformation, :width, args[0])
130
- set_or_yell(transformation, :height, args[1])
131
- set_or_yell(transformation, :gravity, args[2].to_s.downcase)
132
- set_or_yell(transformation, :crop, :crop)
133
- when :cloudinary_transformation
134
- args.each do
135
- |attr, value|
136
- set_or_yell(transformation, attr, value)
137
- end
138
- end
139
- end
140
- @transformation = transformation
141
- @transformation
142
- end
143
-
144
- def eager
145
- @eager ||= self.class.processors.any?{|processor| processor[0] == :eager}
146
- end
147
-
148
- def tags
149
- @tags ||= self.class.processors.select{|processor| processor[0] == :tags}.map(&:last).first
150
- end
14
+ base.send(:attr_reader, :stored_version)
15
+
16
+ override_in_versions(base, :blank?, :full_public_id)
17
+ end
151
18
 
152
- def format
153
- format_processor = self.class.processors.find{|processor| processor[0] == :convert}
154
- if format_processor
155
- if format_processor[1].is_a?(Array)
156
- return format_processor[1][0]
157
- end
158
- return format_processor[1]
19
+ def retrieve_from_store!(identifier)
20
+ if identifier.blank?
21
+ @file = @stored_version = @stored_public_id = nil
22
+ self.original_filename = nil
23
+ else
24
+ @file = CloudinaryFile.new(identifier, self)
25
+ @public_id = @stored_public_id = @file.public_id
26
+ @stored_version = @file.version
27
+ self.original_filename = @file.filename
159
28
  end
160
- the_filename = original_filename || stored_filename
161
- return the_filename.split(".").last if the_filename.include?(".")
162
- "png" # TODO Default format?
163
- end
164
-
29
+ end
30
+
165
31
  def url(*args)
166
32
  if args.first && !args.first.is_a?(Hash)
167
33
  super
168
34
  else
169
- return nil if self.my_identifier.blank?
35
+ return super if self.blank?
170
36
  options = args.extract_options!
171
- options = options.merge(self.class.version_names.blank? ? {} : self.transformation)
172
- Cloudinary::Utils.cloudinary_url(self.my_filename, options)
37
+ options = self.transformation.merge(options) if self.version_name.present?
38
+ Cloudinary::Utils.cloudinary_url(self.full_public_id, {:format=>self.format}.merge(options))
173
39
  end
174
40
  end
41
+
42
+ def full_public_id
43
+ return nil if self.blank?
44
+ return self.my_public_id if self.stored_version.blank?
45
+ return "v#{self.stored_version}/#{self.my_public_id}"
46
+ end
175
47
 
176
- def recreate_versions!
177
- # Do nothing
178
- end
179
-
180
- def process!(new_file=nil)
181
- # Do nothing
182
- end
183
-
184
- def stored_filename
185
- model.read_uploader(mounted_as)
186
- end
187
-
188
- def my_filename
189
- return stored_filename if stored_filename.present?
190
- @my_filename ||= "#{self.public_id}.#{self.format}"
48
+ def filename
49
+ return nil if self.blank?
50
+ return [self.full_public_id, self.format].join(".")
191
51
  end
192
-
52
+
53
+ # public_id to use for uploaded file. Can be overridden by caller. Random public_id will be used otherwise.
193
54
  def public_id
194
- return @public_id if @public_id
195
- if stored_filename.present?
196
- last_dot = stored_filename.rindex(".")
197
- @public_id = last_dot ? stored_filename[0, last_dot] : stored_filename
198
- end
55
+ nil
56
+ end
57
+
58
+ # If the user overrode public_id, that should be used, even if it's different from current public_id in the database.
59
+ # Otherwise, try to use public_id from the database.
60
+ # Otherwise, generate a new random public_id
61
+ def my_public_id
62
+ @public_id ||= self.public_id
63
+ @public_id ||= @stored_public_id
199
64
  @public_id ||= Cloudinary::Utils.random_public_id
200
65
  end
201
-
202
- def download!(uri)
203
- uri = process_uri(uri)
204
- self.original_filename = @cache_id = @filename = File.basename(uri.path).gsub(/[^a-zA-Z0-9\.\-\+_]/, '')
205
- @file = RemoteFile.new(uri, @filename)
66
+
67
+ def recreate_versions!
68
+ # Do nothing
206
69
  end
207
70
 
208
- def my_identifier
209
- (self.filename || self.stored_filename.present?) ? self.my_filename : nil
71
+ def cache_versions!(new_file=nil)
72
+ # Do nothing
210
73
  end
211
74
 
212
- class RemoveableFile
213
- def initialize(identifier)
214
- @public_id = identifier.split("/").last.split(".").first
215
- end
216
-
217
- def delete
218
- Cloudinary::Uploader.destroy(@public_id)
219
- end
75
+ def process!(new_file=nil)
76
+ # Do nothing
220
77
  end
221
78
 
222
- class RemoteFile
223
- attr_reader :uri, :original_filename
224
- def initialize(uri, filename)
225
- @uri = uri
226
- @original_filename = filename
227
- end
228
-
229
- def delete
230
- # Do nothing. This is a virtual file.
231
- end
79
+ # Should removed files be removed from Cloudinary as well. Can be overridden.
80
+ def delete_remote?
81
+ true
232
82
  end
233
83
 
234
84
  class CloudinaryFile
235
- attr_reader :original_filename, :version
236
- def initialize(file_info)
237
- resource_type, @version, @original_filename, @signature = file_info.scan(CLOUDINARY_PATH).first
238
- raise "Cloudinary CarrierWave integration supports images only" if resource_type != "image"
239
- public_id = @original_filename[0..(@original_filename.rindex(".")-1)]
240
- expected_signature = Cloudinary::Utils.api_sign_request({:public_id=>public_id, :version=>version}, Cloudinary.config.api_secret)
241
- if @signature != expected_signature
242
- raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.cloudinary_signature_error", :public_id=>public_id, :default=>"Invalid signature for #{public_id}")
85
+ attr_reader :identifier, :public_id, :filename, :format, :version
86
+ def initialize(identifier, uploader)
87
+ @uploader = uploader
88
+ @identifier = identifier
89
+ if @identifier.include?("/")
90
+ version, @filename = @identifier.split("/")
91
+ @version = version[1..-1] # remove 'v' prefix
92
+ else
93
+ @filename = @identifier
94
+ @version = nil
243
95
  end
96
+ @public_id, @format = Cloudinary::CarrierWave.split_format(@filename)
244
97
  end
245
98
 
246
- def to_s
247
- "image/upload/v#{@version}/#{@original_filename}##{@signature}"
248
- end
249
-
250
99
  def delete
251
- # Do nothing. This is a virtual file.
100
+ Cloudinary::Uploader.destroy(self.public_id) if @uploader.delete_remote?
252
101
  end
253
102
  end
254
-
255
- class Storage < ::CarrierWave::Storage::Abstract
256
- def store!(file)
257
- # Moved to identifier...
258
- if uploader.class.version_names.blank?
259
- return store_cloudinary_version(file.version) if file.is_a?(CloudinaryFile)
260
-
261
- # This is the toplevel, need to upload the actual file.
262
- params = uploader.transformation.dup
263
- params[:return_error] = true
264
- params[:format] = uploader.format
265
- params[:public_id] = uploader.public_id.split("/").last
266
- params[:tags] = uploader.tags if uploader.tags
267
- eager_versions = uploader.versions.values.select(&:eager)
268
- params[:eager] = eager_versions.map{|version| [version.transformation, version.format]} if eager_versions.length > 0
269
-
270
- data = nil
271
- if (file.is_a?(RemoteFile))
272
- data = file.uri.to_s
273
- else
274
- data = file.file
275
- data.rewind if !file.is_path? && data.respond_to?(:rewind)
276
- end
277
- uploader.metadata = Cloudinary::Uploader.upload(data, params)
278
- if uploader.metadata["error"]
279
- raise UploadError.new(uploader.metadata["error"]["message"], uploader.metadata["error"]["http_code"])
280
- end
281
-
282
- store_cloudinary_version(uploader.metadata["version"]) if uploader.metadata["version"]
283
- # Will throw an exception on error
284
- else
285
- raise "nested versions are not allowed." if (uploader.class.version_names.length > 1)
286
- # Do nothing
287
- end
288
- nil
289
- end
290
-
291
- def store_cloudinary_version(version)
292
- name = "v#{version}/#{identifier.split("/").last}"
293
- model_class = uploader.model.class
294
- if defined?(ActiveRecord::Base) && uploader.model.is_a?(ActiveRecord::Base)
295
- primary_key = model_class.primary_key.to_sym
296
- model_class.update_all({uploader.mounted_as=>name}, {primary_key=>uploader.model.send(primary_key)})
297
- uploader.model.send :write_attribute, uploader.mounted_as, name
298
- elsif model_class.respond_to?(:update_all) && uploader.model.respond_to?(:_id)
299
- # Mongoid support
300
- model_class.where(:_id=>uploader.model._id).update_all(uploader.mounted_as=>name)
301
- uploader.model.send :write_attribute, uploader.mounted_as, name
302
- else
303
- raise "Only ActiveRecord and Mongoid are supported at the moment!"
103
+
104
+ def self.split_format(identifier)
105
+ last_dot = identifier.rindex(".")
106
+ return [public_id, nil] if last_dot.nil?
107
+ public_id = identifier[0, last_dot]
108
+ format = identifier[last_dot+1..-1]
109
+ return [public_id, format]
110
+ end
111
+
112
+ # For the given methods - versions should call the main uploader method
113
+ def self.override_in_versions(base, *methods)
114
+ methods.each do
115
+ |method|
116
+ base.send :define_method, method do
117
+ return super() if self.version_name.blank?
118
+ uploader = self.model.send(self.mounted_as)
119
+ uploader.send(method)
304
120
  end
305
- end
306
-
307
- def retrieve!(identifier)
308
- if uploader.class.version_names.blank?
309
- return RemoveableFile.new(identifier)
310
- else
311
- return nil # Version files should not be deleted.
312
- end
313
- end
314
-
315
- def identifier
316
- uploader.my_identifier
317
- end
121
+ end
318
122
  end
319
123
  end
@@ -0,0 +1,9 @@
1
+ module Cloudinary::CarrierWave
2
+ class UploadError < StandardError
3
+ attr_reader :http_code
4
+ def initialize(message, http_code)
5
+ super(message)
6
+ @http_code = http_code
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,62 @@
1
+ # Copyright Cloudinary
2
+ # Support for store in CarrierWave files that were preloaded to cloudinary (e.g., by javascript)
3
+ # Field value must be in the format: "image/upload/v<version>/#<public_id>.<format>#<signature>"
4
+ # Where signature is the cloduinary API signature on the public_id and version.
5
+ module Cloudinary::CarrierWave
6
+ PRELOADED_CLOUDINARY_PATH = /^([^\/]+)\/upload\/v(\d+)\/([^\/]+)#([^\/]+)$/
7
+
8
+ def cache!(new_file)
9
+ if new_file.is_a?(String) && new_file.match(PRELOADED_CLOUDINARY_PATH)
10
+ @file = PreloadedCloudinaryFile.new(new_file)
11
+ @stored_version = @file.version
12
+ @public_id = @stored_public_id = @file.public_id
13
+ self.original_filename = @file.original_filename
14
+ @cache_id = "unused" # must not be blank
15
+ else
16
+ super
17
+ @public_id = nil # allow overriding public_id
18
+ end
19
+ end
20
+
21
+ def retrieve_from_cache!(new_file)
22
+ if new_file.is_a?(String) && new_file.match(PRELOADED_CLOUDINARY_PATH)
23
+ @file = PreloadedCloudinaryFile.new(new_file)
24
+ @stored_version = @file.version
25
+ @public_id = @stored_public_id = @file.public_id
26
+ self.original_filename = @file.original_filename
27
+ @cache_id = "unused" # must not be blank
28
+ else
29
+ super
30
+ @public_id = nil # allow overriding public_id
31
+ end
32
+ end
33
+
34
+ def cache_name
35
+ return @file.is_a?(PreloadedCloudinaryFile) ? @file.to_s : super
36
+ end
37
+
38
+ class PreloadedCloudinaryFile
39
+ attr_reader :original_filename, :version, :public_id, :signature
40
+ def initialize(file_info)
41
+ resource_type, @version, @original_filename, @signature = file_info.scan(PRELOADED_CLOUDINARY_PATH).first
42
+ raise "Cloudinary CarrierWave integration supports images only" if resource_type != "image"
43
+ @public_id = @original_filename[0..(@original_filename.rindex(".")-1)]
44
+ expected_signature = Cloudinary::Utils.api_sign_request({:public_id=>public_id, :version=>version}, Cloudinary.config.api_secret)
45
+ if @signature != expected_signature
46
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.cloudinary_signature_error", :public_id=>public_id, :default=>"Invalid signature for #{public_id}")
47
+ end
48
+ end
49
+
50
+ def identifier
51
+ "v#{version}/#{original_filename}"
52
+ end
53
+
54
+ def to_s
55
+ "image/upload/v#{version}/#{original_filename}##{signature}"
56
+ end
57
+
58
+ def delete
59
+ # Do nothing. This is a virtual file.
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,117 @@
1
+ module Cloudinary::CarrierWave
2
+ module ClassMethods
3
+ def eager
4
+ process :eager => true
5
+ end
6
+
7
+ def convert(format)
8
+ process :convert => format
9
+ end
10
+
11
+ def resize_to_limit(width, height)
12
+ process :resize_to_limit => [width, height]
13
+ end
14
+
15
+ def resize_to_fit(width, height)
16
+ process :resize_to_fit => [width, height]
17
+ end
18
+
19
+ def resize_to_fill(width, height, gravity="Center")
20
+ process :resize_to_fill => [width, height, gravity]
21
+ end
22
+
23
+ def resize_and_pad(width, height, background=:transparent, gravity="Center")
24
+ process :resize_and_pad => [width, height, background, gravity]
25
+ end
26
+
27
+ def scale(width, height)
28
+ process :scale => [width, height]
29
+ end
30
+
31
+ def crop(width, height, gravity="Center")
32
+ process :crop => [width, height, gravity]
33
+ end
34
+
35
+ def cloudinary_transformation(options)
36
+ process :cloudinary_transformation => options
37
+ end
38
+
39
+ def tags(*tags)
40
+ process :tags=>tags
41
+ end
42
+ end
43
+
44
+ def set_or_yell(hash, attr, value)
45
+ raise "conflicting transformation on #{attr} #{value}!=#{hash[attr]}" if hash[attr]
46
+ hash[attr] = value
47
+ end
48
+
49
+ def transformation
50
+ return @transformation if @transformation
51
+ @transformation = {}
52
+ self.class.processors.each do
53
+ |name, args|
54
+ case name
55
+ when :convert # Do nothing. This is handled by format
56
+ when :resize_to_limit
57
+ set_or_yell(@transformation, :width, args[0])
58
+ set_or_yell(@transformation, :height, args[1])
59
+ set_or_yell(@transformation, :crop, :limit)
60
+ when :resize_to_fit
61
+ set_or_yell(@transformation, :width, args[0])
62
+ set_or_yell(@transformation, :height, args[1])
63
+ set_or_yell(@transformation, :crop, :fit)
64
+ when :resize_to_fill
65
+ set_or_yell(@transformation, :width, args[0])
66
+ set_or_yell(@transformation, :height, args[1])
67
+ set_or_yell(@transformation, :gravity, args[2].to_s.downcase)
68
+ set_or_yell(@transformation, :crop, :fill)
69
+ when :resize_and_pad
70
+ set_or_yell(@transformation, :width, args[0])
71
+ set_or_yell(@transformation, :height, args[1])
72
+ set_or_yell(@transformation, :background, args[2].to_s.downcase)
73
+ set_or_yell(@transformation, :gravity, args[3].to_s.downcase)
74
+ set_or_yell(@transformation, :crop, :pad)
75
+ when :scale
76
+ set_or_yell(@transformation, :width, args[0])
77
+ set_or_yell(@transformation, :height, args[1])
78
+ set_or_yell(@transformation, :crop, :scale)
79
+ when :crop
80
+ set_or_yell(@transformation, :width, args[0])
81
+ set_or_yell(@transformation, :height, args[1])
82
+ set_or_yell(@transformation, :gravity, args[2].to_s.downcase)
83
+ set_or_yell(@transformation, :crop, :crop)
84
+ when :cloudinary_transformation
85
+ args.each do
86
+ |attr, value|
87
+ set_or_yell(@transformation, attr, value)
88
+ end
89
+ end
90
+ end
91
+ @transformation
92
+ end
93
+
94
+ def eager
95
+ @eager ||= self.class.processors.any?{|processor| processor[0] == :eager}
96
+ end
97
+
98
+ def tags
99
+ @tags ||= self.class.processors.select{|processor| processor[0] == :tags}.map(&:last).first
100
+ end
101
+
102
+ def format
103
+ format_processor = self.class.processors.find{|processor| processor[0] == :convert}
104
+ if format_processor
105
+ # Explicit format is given
106
+ return Array(format_processor[1]).first
107
+ elsif self.version_name.present?
108
+ # No local format. The reset should be handled by main uploader
109
+ uploader = self.model.send(self.mounted_as)
110
+ return uploader.format
111
+ else
112
+ # Try to auto-detect format
113
+ format = Cloudinary::CarrierWave.split_format(original_filename || "").last
114
+ return format || "png" # TODO Default format?
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,23 @@
1
+ module Cloudinary::CarrierWave
2
+ def download!(uri)
3
+ if respond_to?(:process_uri)
4
+ uri = process_uri(uri)
5
+ else # Backward compatibility with old CarrierWave
6
+ uri = URI.parse(URI.escape(URI.unescape(uri)))
7
+ end
8
+ self.original_filename = @cache_id = @filename = File.basename(uri.path).gsub(/[^a-zA-Z0-9\.\-\+_]/, '')
9
+ @file = RemoteFile.new(uri, @filename)
10
+ end
11
+
12
+ class RemoteFile
13
+ attr_reader :uri, :original_filename
14
+ def initialize(uri, filename)
15
+ @uri = uri
16
+ @original_filename = filename
17
+ end
18
+
19
+ def delete
20
+ # Do nothing. This is a virtual file.
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
2
+ def store!(file)
3
+ if uploader.class.version_names.blank?
4
+ return store_cloudinary_version(file.version) if file.is_a?(Cloudinary::CarrierWave::PreloadedCloudinaryFile)
5
+
6
+ # This is the toplevel, need to upload the actual file.
7
+ params = uploader.transformation.dup
8
+ params[:return_error] = true
9
+ params[:format] = uploader.format
10
+ params[:public_id] = uploader.my_public_id
11
+ params[:tags] = uploader.tags if uploader.tags
12
+ eager_versions = uploader.versions.values.select(&:eager)
13
+ params[:eager] = eager_versions.map{|version| [version.transformation, version.format]} if eager_versions.length > 0
14
+
15
+ data = nil
16
+ if (file.is_a?(Cloudinary::CarrierWave::RemoteFile))
17
+ data = file.uri.to_s
18
+ else
19
+ data = file.file
20
+ data.rewind if !file.is_path? && data.respond_to?(:rewind)
21
+ end
22
+ uploader.metadata = Cloudinary::Uploader.upload(data, params)
23
+ if uploader.metadata["error"]
24
+ raise Cloudinary::CarrierWave::UploadError.new(uploader.metadata["error"]["message"], uploader.metadata["error"]["http_code"])
25
+ end
26
+
27
+ store_cloudinary_version(uploader.metadata["version"]) if uploader.metadata["version"]
28
+ # Will throw an exception on error
29
+ else
30
+ raise "nested versions are not allowed." if (uploader.class.version_names.length > 1)
31
+ # Do nothing - versions are not handled locally.
32
+ end
33
+ nil
34
+ end
35
+
36
+ def store_cloudinary_version(version)
37
+ name = "v#{version}/#{identifier.split("/").last}"
38
+ model_class = uploader.model.class
39
+ if defined?(ActiveRecord::Base) && uploader.model.is_a?(ActiveRecord::Base)
40
+ primary_key = model_class.primary_key.to_sym
41
+ model_class.update_all({uploader.mounted_as=>name}, {primary_key=>uploader.model.send(primary_key)})
42
+ uploader.model.send :write_attribute, uploader.mounted_as, name
43
+ elsif model_class.respond_to?(:update_all) && uploader.model.respond_to?(:_id)
44
+ # Mongoid support
45
+ model_class.where(:_id=>uploader.model._id).update_all(uploader.mounted_as=>name)
46
+ uploader.model.send :write_attribute, uploader.mounted_as, name
47
+ else
48
+ raise "Only ActiveRecord and Mongoid are supported at the moment!"
49
+ end
50
+ end
51
+ end
@@ -20,7 +20,7 @@ module CloudinaryHelper
20
20
  def cl_image_path(source, options = {})
21
21
  options = options.clone
22
22
  url = cloudinary_url(source, options)
23
- original_image_path(url, options)
23
+ original_image_path(url)
24
24
  end
25
25
 
26
26
  def image_tag(*args)
@@ -188,6 +188,7 @@ rescue LoadError
188
188
  end
189
189
 
190
190
  begin
191
+ require 'sass'
191
192
  require 'sass/script/functions'
192
193
  module Sass::Script::Functions
193
194
  def cloudinary_url(public_id, sass_options={})
@@ -21,6 +21,8 @@ class Cloudinary::Utils
21
21
  y = options.delete(:y)
22
22
  radius = options.delete(:radius)
23
23
  default_image = options.delete(:default_image)
24
+ background = options.delete(:background)
25
+ background = background.sub(/^#/, 'rgb:') if background
24
26
 
25
27
  gravity = options.delete(:gravity)
26
28
  quality = options.delete(:quality)
@@ -36,7 +38,7 @@ class Cloudinary::Utils
36
38
  end
37
39
  prefix = options.delete(:prefix)
38
40
 
39
- params = {:w=>width, :h=>height, :t=>named_transformation, :c=>crop, :q=>quality, :g=>gravity, :p=>prefix, :x=>x, :y=>y, :r=>radius, :d=>default_image}
41
+ params = {:w=>width, :h=>height, :t=>named_transformation, :c=>crop, :q=>quality, :g=>gravity, :p=>prefix, :x=>x, :y=>y, :r=>radius, :d=>default_image, :b=>background}
40
42
  transformation = params.reject{|k,v| v.blank?}.map{|k,v| [k.to_s, v]}.sort_by(&:first).map{|k,v| "#{k}_#{v}"}.join(",")
41
43
  raw_transformation = options.delete(:raw_transformation)
42
44
  transformation = [transformation, raw_transformation].reject(&:blank?).join(",")
@@ -64,6 +66,7 @@ class Cloudinary::Utils
64
66
  secure_distribution = options.delete(:secure_distribution) || Cloudinary.config.secure_distribution
65
67
  force_remote = options.delete(:force_remote)
66
68
 
69
+ return original_source if source.blank?
67
70
  if !force_remote
68
71
  return original_source if (type.nil? || type == :asset) && source.match(%r(^https?:/)i)
69
72
  if source.start_with?("/")
@@ -138,6 +141,10 @@ class Cloudinary::Utils
138
141
  def self.random_public_id
139
142
  (defined?(ActiveSupport::SecureRandom) ? ActiveSupport::SecureRandom : SecureRandom).base64(16).downcase.gsub(/[^a-z0-9]/, "")
140
143
  end
144
+
145
+ def self.signed_preloaded_image(result)
146
+ "#{result["resource_type"]}/upload/v#{result["version"]}/#{[result["public_id"], result["format"]].join(".")}##{result["signature"]}"
147
+ end
141
148
 
142
149
  @@json_decode = false
143
150
  def self.json_decode(str)
@@ -1,4 +1,4 @@
1
1
  # Copyright Cloudinary
2
2
  module Cloudinary
3
- VERSION = "1.0.15"
3
+ VERSION = "1.0.16"
4
4
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: cloudinary
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.0.15
5
+ version: 1.0.16
6
6
  platform: ruby
7
7
  authors:
8
8
  - Nadav Soferman
@@ -12,7 +12,7 @@ autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
14
 
15
- date: 2012-04-23 00:00:00 +03:00
15
+ date: 2012-04-25 00:00:00 +03:00
16
16
  default_executable:
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
@@ -58,6 +58,11 @@ files:
58
58
  - lib/cloudinary.rb
59
59
  - lib/cloudinary/blob.rb
60
60
  - lib/cloudinary/carrier_wave.rb
61
+ - lib/cloudinary/carrier_wave/error.rb
62
+ - lib/cloudinary/carrier_wave/preloaded.rb
63
+ - lib/cloudinary/carrier_wave/process.rb
64
+ - lib/cloudinary/carrier_wave/remote.rb
65
+ - lib/cloudinary/carrier_wave/storage.rb
61
66
  - lib/cloudinary/controller.rb
62
67
  - lib/cloudinary/downloader.rb
63
68
  - lib/cloudinary/helper.rb