carrierwave 0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of carrierwave might be problematic. Click here for more details.

@@ -0,0 +1,80 @@
1
+ module CarrierWave
2
+ module Storage
3
+ ##
4
+ # This file serves mostly as a specification for Storage engines. There is no requirement
5
+ # that storage engines must be a subclass of this class. However, any storage engine must
6
+ # conform to the following interface:
7
+ #
8
+ # The storage engine must respond to store!, taking an uploader object and a
9
+ # CarrierWave::SanitizedFile as parameters. This method should do something to store
10
+ # the given file, and then return an object.
11
+ #
12
+ # The storage engine must respond to retrieve!, taking an uploader object and an identifier
13
+ # as parameters. This method should do retrieve and then return an object.
14
+ #
15
+ # The objects returned by store! and retrieve! both *must* respond to +identifier+, taking
16
+ # no arguments. Identifier is a string that uniquely identifies this file and can be used
17
+ # to retrieve it later.
18
+ #
19
+ class Abstract
20
+
21
+ ##
22
+ # Do setup specific for this storage engine
23
+ #
24
+ def self.setup!; end
25
+
26
+ ##
27
+ # Do something to store the file
28
+ #
29
+ # @param [CarrierWave::Uploader] uploader an uploader object
30
+ # @param [CarrierWave::SanitizedFile] file the file to store
31
+ #
32
+ # @return [#identifier] an object
33
+ #
34
+ def self.store!(uploader, file)
35
+ self.new
36
+ end
37
+
38
+ # Do something to retrieve the file
39
+ #
40
+ # @param [CarrierWave::Uploader] uploader an uploader object
41
+ # @param [String] identifier uniquely identifies the file
42
+ #
43
+ # @return [#identifier] an object
44
+ #
45
+ def self.retrieve!(uploader, identifier)
46
+ self.new
47
+ end
48
+
49
+ ##
50
+ # Should return a String that uniquely identifies this file and can be used to retrieve it from
51
+ # the same storage engine later on.
52
+ #
53
+ # This is OPTIONAL
54
+ #
55
+ # @return [String] path to the file
56
+ #
57
+ def identifier; end
58
+
59
+ ##
60
+ # Should return the url where the file is publically accessible. If this is not set, then
61
+ # it is assumed that the url is the path relative to the public directory.
62
+ #
63
+ # This is OPTIONAL
64
+ #
65
+ # @return [String] file's url
66
+ #
67
+ def url; end
68
+
69
+ ##
70
+ # Should return the path where the file is corrently located. This is OPTIONAL.
71
+ #
72
+ # This is OPTIONAL
73
+ #
74
+ # @return [String] path to the file
75
+ #
76
+ def path; end
77
+
78
+ end # Abstract
79
+ end # Storage
80
+ end # CarrierWave
@@ -0,0 +1,40 @@
1
+ module CarrierWave
2
+ module Storage
3
+ class File < Abstract
4
+
5
+ def initialize(uploader)
6
+ @uploader = uploader
7
+ end
8
+
9
+ ##
10
+ # Move the file to the uploader's store path.
11
+ #
12
+ # @param [CarrierWave::Uploader] uploader an uploader object
13
+ # @param [CarrierWave::SanitizedFile] file the file to store
14
+ #
15
+ # @return [CarrierWave::SanitizedFile] a sanitized file
16
+ #
17
+ def self.store!(uploader, file)
18
+ path = ::File.join(uploader.store_dir, uploader.filename)
19
+ path = ::File.expand_path(path, uploader.public)
20
+ file.move_to(path)
21
+ file
22
+ end
23
+
24
+ ##
25
+ # Retrieve the file from its store path
26
+ #
27
+ # @param [CarrierWave::Uploader] uploader an uploader object
28
+ # @param [String] identifier the filename of the file
29
+ #
30
+ # @return [CarrierWave::SanitizedFile] a sanitized file
31
+ #
32
+ def self.retrieve!(uploader, identifier)
33
+ path = ::File.join(uploader.store_dir, identifier)
34
+ path = ::File.expand_path(path, uploader.public)
35
+ CarrierWave::SanitizedFile.new(path)
36
+ end
37
+
38
+ end # File
39
+ end # Storage
40
+ end # CarrierWave
@@ -0,0 +1,83 @@
1
+ module CarrierWave
2
+ module Storage
3
+ ##
4
+ # Uploads things to Amazon S3 webservices
5
+ #
6
+ class S3 < Abstract
7
+
8
+ def initialize(bucket, store_dir, identifier)
9
+ @bucket = bucket
10
+ @store_dir = store_dir
11
+ @identifier = identifier
12
+ end
13
+
14
+ ##
15
+ # Connect to Amazon S3
16
+ #
17
+ def self.setup!
18
+ require 'aws/s3'
19
+ AWS::S3::Base.establish_connection!(
20
+ :access_key_id => CarrierWave.config[:s3][:access_key_id],
21
+ :secret_access_key => CarrierWave.config[:s3][:secret_access_key]
22
+ )
23
+ end
24
+
25
+ ##
26
+ # @return [String] the bucket set in the config options
27
+ #
28
+ def self.bucket
29
+ CarrierWave.config[:s3][:bucket]
30
+ end
31
+
32
+ ##
33
+ # @return [Symbol] the access priviliges the uploaded files should have
34
+ #
35
+ def self.access
36
+ CarrierWave.config[:s3][:access]
37
+ end
38
+
39
+ ##
40
+ # Store the file on S3
41
+ #
42
+ # @param [CarrierWave::Uploader] uploader an uploader object
43
+ # @param [CarrierWave::SanitizedFile] file the file to store
44
+ #
45
+ # @return [#identifier] an object
46
+ #
47
+ def self.store!(uploader, file)
48
+ AWS::S3::S3Object.store(::File.join(uploader.store_dir, uploader.filename), file.read, bucket, :access => access)
49
+ self.new(bucket, uploader.store_dir, uploader.filename)
50
+ end
51
+
52
+ # Do something to retrieve the file
53
+ #
54
+ # @param [CarrierWave::Uploader] uploader an uploader object
55
+ # @param [String] identifier uniquely identifies the file
56
+ #
57
+ # @return [#identifier] an object
58
+ #
59
+ def self.retrieve!(uploader, identifier)
60
+ self.new(bucket, uploader.store_dir, identifier)
61
+ end
62
+
63
+ ##
64
+ # Returns the filename on S3
65
+ #
66
+ # @return [String] path to the file
67
+ #
68
+ def identifier
69
+ @identifier
70
+ end
71
+
72
+ ##
73
+ # Returns the url on Amazon's S3 service
74
+ #
75
+ # @return [String] file's url
76
+ #
77
+ def url
78
+ "http://s3.amazonaws.com/#{self.class.bucket}/#{@store_dir}/#{@identifier}"
79
+ end
80
+
81
+ end # S3
82
+ end # Storage
83
+ end # CarrierWave
@@ -0,0 +1,427 @@
1
+ module CarrierWave
2
+ class Uploader
3
+
4
+ class << self
5
+
6
+ ##
7
+ # Returns a list of processor callbacks which have been declared for this uploader
8
+ #
9
+ # @return [String]
10
+ #
11
+ def processors
12
+ @processors ||= []
13
+ end
14
+
15
+ ##
16
+ # Adds a processor callback which applies operations as a file is uploaded.
17
+ # The argument may be the name of any method of the uploader, expressed as a symbol,
18
+ # or a list of such methods, or a hash where the key is a method and the value is
19
+ # an array of arguments to call the method with
20
+ #
21
+ # @param [*Symbol, Hash{Symbol => Array[]}] args
22
+ # @example
23
+ # class MyUploader < CarrierWave::Uploader
24
+ # process :sepiatone, :vignette
25
+ # process :scale => [200, 200]
26
+ #
27
+ # def sepiatone
28
+ # ...
29
+ # end
30
+ #
31
+ # def vignette
32
+ # ...
33
+ # end
34
+ #
35
+ # def scale(height, width)
36
+ # ...
37
+ # end
38
+ # end
39
+ #
40
+ def process(*args)
41
+ args.each do |arg|
42
+ if arg.is_a?(Hash)
43
+ arg.each do |method, args|
44
+ processors.push([method, args])
45
+ end
46
+ else
47
+ processors.push([arg, []])
48
+ end
49
+ end
50
+ end
51
+
52
+ ##
53
+ # Sets the storage engine to be used when storing files with this uploader.
54
+ # Can be any class that implements a #store!(CarrierWave::SanitizedFile) and a #retrieve!
55
+ # method. See lib/carrierwave/storage/file.rb for an example. Storage engines should
56
+ # be added to CarrierWave.config[:storage_engines] so they can be referred
57
+ # to by a symbol, which should be more convenient
58
+ #
59
+ # If no argument is given, it will simply return the currently used storage engine.
60
+ #
61
+ # @param [Symbol, Class] storage The storage engine to use for this uploader
62
+ # @return [Class] the storage engine to be used with this uploader
63
+ # @example
64
+ # storage :file
65
+ # storage CarrierWave::Storage::File
66
+ # storage MyCustomStorageEngine
67
+ #
68
+ def storage(storage = nil)
69
+ if storage.is_a?(Symbol)
70
+ @storage = get_storage_by_symbol(storage)
71
+ @storage.setup!
72
+ elsif storage
73
+ @storage = storage
74
+ @storage.setup!
75
+ elsif @storage.nil?
76
+ # Get the storage from the superclass if there is one
77
+ @storage = superclass.storage rescue nil
78
+ end
79
+ if @storage.nil?
80
+ # If we were not able to find a store any other way, setup the default store
81
+ @storage ||= get_storage_by_symbol(CarrierWave.config[:storage])
82
+ @storage.setup!
83
+ end
84
+ return @storage
85
+ end
86
+
87
+ alias_method :storage=, :storage
88
+
89
+ attr_accessor :version_name
90
+
91
+ ##
92
+ # Adds a new version to this uploader
93
+ #
94
+ # @param [#to_sym] name name of the version
95
+ # @param [Proc] &block a block to eval on this version of the uploader
96
+ #
97
+ def version(name, &block)
98
+ name = name.to_sym
99
+ klass = Class.new(self)
100
+ klass.version_name = name
101
+ klass.class_eval(&block) if block
102
+ versions[name] = klass
103
+ class_eval <<-RUBY
104
+ def #{name}
105
+ versions[:#{name}]
106
+ end
107
+ RUBY
108
+ end
109
+
110
+ ##
111
+ # @return [Hash{Symbol => Class}] a list of versions available for this uploader
112
+ #
113
+ def versions
114
+ @versions ||= {}
115
+ end
116
+
117
+ ##
118
+ # Generates a unique cache id for use in the caching system
119
+ #
120
+ # @return [String] a cache if in the format YYYYMMDD-HHMM-PID-RND
121
+ #
122
+ def generate_cache_id
123
+ Time.now.strftime('%Y%m%d-%H%M') + '-' + Process.pid.to_s + '-' + ("%04d" % rand(9999))
124
+ end
125
+
126
+ private
127
+
128
+ def get_storage_by_symbol(symbol)
129
+ CarrierWave.config[:storage_engines][symbol]
130
+ end
131
+
132
+ end # class << self
133
+
134
+ attr_reader :file, :model, :mounted_as
135
+
136
+ ##
137
+ # If a model is given as the first parameter, it will stored in the uploader, and
138
+ # available throught +#model+. Likewise, mounted_as stores the name of the column
139
+ # where this instance of the uploader is mounted. These values can then be used inside
140
+ # your uploader.
141
+ #
142
+ # If you do not wish to mount your uploaders with the ORM extensions in -more then you
143
+ # can override this method inside your uploader.
144
+ #
145
+ # @param [Object] model Any kind of model object
146
+ # @param [Symbol] mounted_as The name of the column where this uploader is mounted
147
+ # @example
148
+ # class MyUploader < CarrierWave::Uploader
149
+ # def store_dir
150
+ # File.join('public', 'files', mounted_as, model.permalink)
151
+ # end
152
+ # end
153
+ #
154
+ def initialize(model=nil, mounted_as=nil)
155
+ @model = model
156
+ @mounted_as = mounted_as
157
+ end
158
+
159
+ ##
160
+ # @return [Boolean] Whether the uploaded file is blank
161
+ #
162
+ def blank?
163
+ !file or file.empty?
164
+ end
165
+
166
+ ##
167
+ # Apply all process callbacks added through CarrierWave.process
168
+ #
169
+ def process!
170
+ self.class.processors.each do |method, args|
171
+ self.send(method, *args)
172
+ end
173
+ end
174
+
175
+ ##
176
+ # @return [String] the path where the file is currently located.
177
+ #
178
+ def current_path
179
+ file.path if file.respond_to?(:path)
180
+ end
181
+
182
+ ##
183
+ # Returns a hash mapping the name of each version of the uploader to an instance of it
184
+ #
185
+ # @return [Hash{Symbol => CarrierWave::Uploader}] a list of uploader instances
186
+ #
187
+ def versions
188
+ return @versions if @versions
189
+ @versions = {}
190
+ self.class.versions.each do |name, klass|
191
+ @versions[name] = klass.new(model, mounted_as)
192
+ end
193
+ @versions
194
+ end
195
+
196
+ ##
197
+ # @return [String] the location where this file is accessible via a url
198
+ #
199
+ def url
200
+ if file.respond_to?(:url) and not file.url.blank?
201
+ file.url
202
+ elsif current_path
203
+ File.expand_path(current_path).gsub(File.expand_path(public), '')
204
+ end
205
+ end
206
+
207
+ alias_method :to_s, :url
208
+
209
+ ##
210
+ # Returns a string that uniquely identifies the last stored file
211
+ #
212
+ # @return [String] uniquely identifies a file
213
+ #
214
+ def identifier
215
+ file.identifier if file.respond_to?(:identifier)
216
+ end
217
+
218
+ ##
219
+ # Override this in your Uploader to change the filename.
220
+ #
221
+ # Be careful using record ids as filenames. If the filename is stored in the database
222
+ # the record id will be nil when the filename is set. Don't use record ids unless you
223
+ # understand this limitation.
224
+ #
225
+ # Do not use the version_name in the filename, as it will prevent versions from being
226
+ # loaded correctly.
227
+ #
228
+ # @return [String] a filename
229
+ #
230
+ def filename
231
+ @filename
232
+ end
233
+
234
+ ##
235
+ # @return [String] the name of this version of the uploader
236
+ #
237
+ def version_name
238
+ self.class.version_name
239
+ end
240
+
241
+ ##
242
+ # @return [String] the directory that is the root of the application
243
+ #
244
+ def root
245
+ CarrierWave.config[:root]
246
+ end
247
+
248
+ ##
249
+ # @return [String] the directory where files will be publically accessible
250
+ #
251
+ def public
252
+ CarrierWave.config[:public]
253
+ end
254
+
255
+ ####################
256
+ ## Cache
257
+ ####################
258
+
259
+ ##
260
+ # Override this in your Uploader to change the directory where files are cached.
261
+ #
262
+ # @return [String] a directory
263
+ #
264
+ def cache_dir
265
+ CarrierWave.config[:cache_dir]
266
+ end
267
+
268
+ ##
269
+ # Returns a String which uniquely identifies the currently cached file for later retrieval
270
+ #
271
+ # @return [String] a cache name, in the format YYYYMMDD-HHMM-PID-RND/filename.txt
272
+ #
273
+ def cache_name
274
+ File.join(cache_id, [version_name, original_filename].compact.join('_')) if cache_id and original_filename
275
+ end
276
+
277
+ ##
278
+ # Caches the given file unless a file has already been cached, stored or retrieved.
279
+ #
280
+ # @param [File, IOString, Tempfile] new_file any kind of file object
281
+ # @raise [CarrierWave::FormNotMultipart] if the assigned parameter is a string
282
+ #
283
+ def cache(new_file)
284
+ cache!(new_file) unless file
285
+ end
286
+
287
+ ##
288
+ # Caches the given file. Calls process! to trigger any process callbacks.
289
+ #
290
+ # @param [File, IOString, Tempfile] new_file any kind of file object
291
+ # @raise [CarrierWave::FormNotMultipart] if the assigned parameter is a string
292
+ #
293
+ def cache!(new_file)
294
+ new_file = CarrierWave::SanitizedFile.new(new_file)
295
+ raise CarrierWave::FormNotMultipart if new_file.string?
296
+
297
+ unless new_file.empty?
298
+ self.cache_id = CarrierWave::Uploader.generate_cache_id unless cache_id
299
+
300
+ @file = new_file
301
+
302
+ @filename = new_file.filename
303
+ self.original_filename = new_file.filename
304
+
305
+ @file = @file.copy_to(cache_path)
306
+ process!
307
+
308
+ versions.each do |name, v|
309
+ v.send(:cache_id=, cache_id)
310
+ v.cache!(new_file)
311
+ end
312
+ end
313
+ end
314
+
315
+ ##
316
+ # Retrieves the file with the given cache_name from the cache, unless a file has
317
+ # already been cached, stored or retrieved.
318
+ #
319
+ # @param [String] cache_name uniquely identifies a cache file
320
+ #
321
+ def retrieve_from_cache(cache_name)
322
+ retrieve_from_cache!(cache_name) unless file
323
+ rescue CarrierWave::InvalidParameter
324
+ end
325
+
326
+ ##
327
+ # Retrieves the file with the given cache_name from the cache.
328
+ #
329
+ # @param [String] cache_name uniquely identifies a cache file
330
+ # @raise [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
331
+ #
332
+ def retrieve_from_cache!(cache_name)
333
+ self.cache_id, self.original_filename = cache_name.split('/', 2)
334
+ @filename = original_filename
335
+ @file = CarrierWave::SanitizedFile.new(cache_path)
336
+ versions.each { |name, v| v.retrieve_from_cache!(cache_name) }
337
+ end
338
+
339
+ ####################
340
+ ## STORE
341
+ ####################
342
+
343
+ ##
344
+ # Override this in your Uploader to change the directory where the file backend stores files.
345
+ #
346
+ # Other backends may or may not use this method, depending on their specific needs.
347
+ #
348
+ # @return [String] a directory
349
+ #
350
+ def store_dir
351
+ [CarrierWave.config[:store_dir], version_name].compact.join(File::Separator)
352
+ end
353
+
354
+ ##
355
+ # Stores the file by passing it to this Uploader's storage engine, unless a file has
356
+ # already been cached, stored or retrieved.
357
+ #
358
+ # If CarrierWave.config[:use_cache] is true, it will first cache the file
359
+ # and apply any process callbacks before uploading it.
360
+ #
361
+ # @param [File, IOString, Tempfile] new_file any kind of file object
362
+ #
363
+ def store(new_file)
364
+ store!(new_file) unless file
365
+ end
366
+
367
+ ##
368
+ # Stores the file by passing it to this Uploader's storage engine.
369
+ #
370
+ # If new_file is omitted, a previously cached file will be stored.
371
+ #
372
+ # @param [File, IOString, Tempfile] new_file any kind of file object
373
+ #
374
+ def store!(new_file=nil)
375
+ cache!(new_file) if new_file
376
+ if @file
377
+ @file = storage.store!(self, @file)
378
+ @cache_id = nil
379
+ versions.each { |name, v| v.store!(new_file) }
380
+ end
381
+ end
382
+
383
+ ##
384
+ # Retrieves the file from the storage, unless a file has
385
+ # already been cached, stored or retrieved.
386
+ #
387
+ # @param [String] identifier uniquely identifies the file to retrieve
388
+ #
389
+ def retrieve_from_store(identifier)
390
+ retrieve_from_store!(identifier) unless file
391
+ rescue CarrierWave::InvalidParameter
392
+ end
393
+
394
+ ##
395
+ # Retrieves the file from the storage.
396
+ #
397
+ # @param [String] identifier uniquely identifies the file to retrieve
398
+ #
399
+ def retrieve_from_store!(identifier)
400
+ @file = storage.retrieve!(self, identifier)
401
+ versions.each { |name, v| v.retrieve_from_store!(identifier) }
402
+ end
403
+
404
+ private
405
+
406
+ def cache_path
407
+ File.expand_path(File.join(cache_dir, cache_name), public)
408
+ end
409
+
410
+ def storage
411
+ self.class.storage
412
+ end
413
+
414
+ attr_reader :cache_id, :original_filename
415
+
416
+ def cache_id=(cache_id)
417
+ raise CarrierWave::InvalidParameter, "invalid cache id" unless cache_id =~ /^[\d]{8}\-[\d]{4}\-[\d]+\-[\d]{4}$/
418
+ @cache_id = cache_id
419
+ end
420
+
421
+ def original_filename=(filename)
422
+ raise CarrierWave::InvalidParameter, "invalid filename" unless filename =~ /^[a-z0-9\.\-\+_]+$/i
423
+ @original_filename = filename
424
+ end
425
+
426
+ end # Uploader
427
+ end # CarrierWave