carrierwave 0.1 → 0.2.0

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.

@@ -1,5 +1,6 @@
1
1
  module CarrierWave
2
2
  module Storage
3
+
3
4
  ##
4
5
  # This file serves mostly as a specification for Storage engines. There is no requirement
5
6
  # that storage engines must be a subclass of this class. However, any storage engine must
@@ -26,10 +27,14 @@ module CarrierWave
26
27
  ##
27
28
  # Do something to store the file
28
29
  #
29
- # @param [CarrierWave::Uploader] uploader an uploader object
30
- # @param [CarrierWave::SanitizedFile] file the file to store
30
+ # === Parameters
31
+ #
32
+ # [uploader (CarrierWave::Uploader)] an uploader object
33
+ # [file (CarrierWave::SanitizedFile)] the file to store
31
34
  #
32
- # @return [#identifier] an object
35
+ # === Returns
36
+ #
37
+ # [#identifier] an object
33
38
  #
34
39
  def self.store!(uploader, file)
35
40
  self.new
@@ -37,10 +42,14 @@ module CarrierWave
37
42
 
38
43
  # Do something to retrieve the file
39
44
  #
40
- # @param [CarrierWave::Uploader] uploader an uploader object
41
- # @param [String] identifier uniquely identifies the file
45
+ # === Parameters
46
+ #
47
+ # [uploader (CarrierWave::Uploader)] an uploader object
48
+ # [identifier (String)] uniquely identifies the file
42
49
  #
43
- # @return [#identifier] an object
50
+ # === Returns
51
+ #
52
+ # [#identifier] an object
44
53
  #
45
54
  def self.retrieve!(uploader, identifier)
46
55
  self.new
@@ -52,7 +61,9 @@ module CarrierWave
52
61
  #
53
62
  # This is OPTIONAL
54
63
  #
55
- # @return [String] path to the file
64
+ # === Returns
65
+ #
66
+ # [String] path to the file
56
67
  #
57
68
  def identifier; end
58
69
 
@@ -62,7 +73,9 @@ module CarrierWave
62
73
  #
63
74
  # This is OPTIONAL
64
75
  #
65
- # @return [String] file's url
76
+ # === Returns
77
+ #
78
+ # [String] file's url
66
79
  #
67
80
  def url; end
68
81
 
@@ -71,7 +84,9 @@ module CarrierWave
71
84
  #
72
85
  # This is OPTIONAL
73
86
  #
74
- # @return [String] path to the file
87
+ # === Returns
88
+ #
89
+ # [String] path to the file
75
90
  #
76
91
  def path; end
77
92
 
@@ -1,5 +1,11 @@
1
1
  module CarrierWave
2
2
  module Storage
3
+
4
+ ##
5
+ # File storage stores file to the Filesystem (surprising, no?). There's really not much
6
+ # to it, it uses the store_dir defined on the uploader as the storage location. That's
7
+ # pretty much it.
8
+ #
3
9
  class File < Abstract
4
10
 
5
11
  def initialize(uploader)
@@ -9,28 +15,36 @@ module CarrierWave
9
15
  ##
10
16
  # Move the file to the uploader's store path.
11
17
  #
12
- # @param [CarrierWave::Uploader] uploader an uploader object
13
- # @param [CarrierWave::SanitizedFile] file the file to store
18
+ # === Parameters
14
19
  #
15
- # @return [CarrierWave::SanitizedFile] a sanitized file
20
+ # [uploader (CarrierWave::Uploader)] an uploader object
21
+ # [file (CarrierWave::SanitizedFile)] the file to store
22
+ #
23
+ # === Returns
24
+ #
25
+ # [CarrierWave::SanitizedFile] a sanitized file
16
26
  #
17
27
  def self.store!(uploader, file)
18
- path = ::File.join(uploader.store_dir, uploader.filename)
28
+ path = ::File.join(uploader.store_path)
19
29
  path = ::File.expand_path(path, uploader.public)
20
- file.move_to(path)
30
+ file.move_to(path, CarrierWave.config[:permissions])
21
31
  file
22
32
  end
23
33
 
24
34
  ##
25
35
  # Retrieve the file from its store path
26
36
  #
27
- # @param [CarrierWave::Uploader] uploader an uploader object
28
- # @param [String] identifier the filename of the file
37
+ # === Parameters
38
+ #
39
+ # [uploader (CarrierWave::Uploader)] an uploader object
40
+ # [identifier (String)] the filename of the file
41
+ #
42
+ # === Returns
29
43
  #
30
- # @return [CarrierWave::SanitizedFile] a sanitized file
44
+ # [CarrierWave::SanitizedFile] a sanitized file
31
45
  #
32
46
  def self.retrieve!(uploader, identifier)
33
- path = ::File.join(uploader.store_dir, identifier)
47
+ path = ::File.join(uploader.store_path(identifier))
34
48
  path = ::File.expand_path(path, uploader.public)
35
49
  CarrierWave::SanitizedFile.new(path)
36
50
  end
@@ -1,13 +1,36 @@
1
1
  module CarrierWave
2
2
  module Storage
3
+
3
4
  ##
4
- # Uploads things to Amazon S3 webservices
5
+ # Uploads things to Amazon S3 webservices. It requies the aws/s3 gem. In order for
6
+ # CarrierWave to connect to Amazon S3, you'll need to specify an access key id, secret key
7
+ # and bucket
8
+ #
9
+ # CarrierWave.config[:s3][:access_key_id] = "xxxxxx"
10
+ # CarrierWave.config[:s3][:secret_access_key] = "xxxxxx"
11
+ # CarrierWave.config[:s3][:bucket] = "my_bucket_name"
12
+ #
13
+ # You can also set the access policy for the uploaded files:
14
+ #
15
+ # CarrierWave.config[:s3][:access] = :public_read
16
+ #
17
+ # Possible values are the 'canned access control policies' provided in the aws/s3 gem,
18
+ # they are:
19
+ #
20
+ # [:private] No one else has any access rights.
21
+ # [:public_read] The anonymous principal is granted READ access.
22
+ # If this policy is used on an object, it can be read from a
23
+ # browser with no authentication.
24
+ # [:public_read_write] The anonymous principal is granted READ and WRITE access.
25
+ # [:authenticated_read] Any principal authenticated as a registered Amazon S3 user
26
+ # is granted READ access.
27
+ #
28
+ # The default is :public_read, it should work in most cases.
5
29
  #
6
30
  class S3 < Abstract
7
31
 
8
- def initialize(bucket, store_dir, identifier)
9
- @bucket = bucket
10
- @store_dir = store_dir
32
+ def initialize(store_path, identifier)
33
+ @store_path = store_path
11
34
  @identifier = identifier
12
35
  end
13
36
 
@@ -23,14 +46,18 @@ module CarrierWave
23
46
  end
24
47
 
25
48
  ##
26
- # @return [String] the bucket set in the config options
49
+ # === Returns
50
+ #
51
+ # [String] the bucket set in the config options
27
52
  #
28
53
  def self.bucket
29
54
  CarrierWave.config[:s3][:bucket]
30
55
  end
31
56
 
32
57
  ##
33
- # @return [Symbol] the access priviliges the uploaded files should have
58
+ # === Returns
59
+ #
60
+ # [Symbol] the access priviliges the uploaded files should have
34
61
  #
35
62
  def self.access
36
63
  CarrierWave.config[:s3][:access]
@@ -39,14 +66,18 @@ module CarrierWave
39
66
  ##
40
67
  # Store the file on S3
41
68
  #
42
- # @param [CarrierWave::Uploader] uploader an uploader object
43
- # @param [CarrierWave::SanitizedFile] file the file to store
69
+ # === Parameters
70
+ #
71
+ # [uploader (CarrierWave::Uploader)] an uploader object
72
+ # [file (CarrierWave::SanitizedFile)] the file to store
44
73
  #
45
- # @return [#identifier] an object
74
+ # === Returns
75
+ #
76
+ # [CarrierWave::Storage::S3] the stored file
46
77
  #
47
78
  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)
79
+ AWS::S3::S3Object.store(::File.join(uploader.store_path), file.read, bucket, :access => access)
80
+ self.new(uploader.store_dir, uploader.filename)
50
81
  end
51
82
 
52
83
  # Do something to retrieve the file
@@ -54,28 +85,48 @@ module CarrierWave
54
85
  # @param [CarrierWave::Uploader] uploader an uploader object
55
86
  # @param [String] identifier uniquely identifies the file
56
87
  #
57
- # @return [#identifier] an object
88
+ # [uploader (CarrierWave::Uploader)] an uploader object
89
+ # [identifier (String)] uniquely identifies the file
90
+ #
91
+ # === Returns
92
+ #
93
+ # [CarrierWave::Storage::S3] the stored file
58
94
  #
59
95
  def self.retrieve!(uploader, identifier)
60
- self.new(bucket, uploader.store_dir, identifier)
96
+ self.new(uploader.store_path(identifier), identifier)
61
97
  end
62
98
 
63
99
  ##
64
100
  # Returns the filename on S3
65
101
  #
66
- # @return [String] path to the file
102
+ # === Returns
103
+ #
104
+ # [String] path to the file
67
105
  #
68
106
  def identifier
69
107
  @identifier
70
108
  end
71
109
 
110
+ ##
111
+ # Reads the contents of the file from S3
112
+ #
113
+ # === Returns
114
+ #
115
+ # [String] contents of the file
116
+ #
117
+ def read
118
+ S3Object.value @store_path, self.class.bucket
119
+ end
120
+
72
121
  ##
73
122
  # Returns the url on Amazon's S3 service
74
123
  #
75
- # @return [String] file's url
124
+ # === Returns
125
+ #
126
+ # [String] file's url
76
127
  #
77
128
  def url
78
- "http://s3.amazonaws.com/#{self.class.bucket}/#{@store_dir}/#{@identifier}"
129
+ ["http://s3.amazonaws.com", self.class.bucket, @store_path].compact.join('/')
79
130
  end
80
131
 
81
132
  end # S3
@@ -0,0 +1,112 @@
1
+ module CarrierWave
2
+ module Test
3
+
4
+ ##
5
+ # These are some matchers that can be used in RSpec specs, to simplify the testing
6
+ # of uploaders.
7
+ #
8
+ module Matchers
9
+
10
+ class BeIdenticalTo
11
+ def initialize(expected)
12
+ @expected = expected
13
+ end
14
+ def matches?(actual)
15
+ @actual = actual
16
+ FileUtils.identical?(@actual, @expected)
17
+ end
18
+ def failure_message
19
+ "expected #{@actual.inspect} to be identical to #{@expected.inspect}"
20
+ end
21
+ def negative_failure_message
22
+ "expected #{@actual.inspect} to not be identical to #{@expected.inspect}"
23
+ end
24
+ end
25
+
26
+ def be_identical_to(expected)
27
+ BeIdenticalTo.new(expected)
28
+ end
29
+
30
+ class HavePermissions
31
+ def initialize(expected)
32
+ @expected = expected
33
+ end
34
+
35
+ def matches?(actual)
36
+ @actual = actual
37
+ # Satisfy expectation here. Return false or raise an error if it's not met.
38
+ (File.stat(@actual.path).mode & 0777) == @expected
39
+ end
40
+
41
+ def failure_message
42
+ "expected #{@actual.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0777).to_s(8)}"
43
+ end
44
+
45
+ def negative_failure_message
46
+ "expected #{@actual.inspect} not to have permissions #{@expected.to_s(8)}, but it did"
47
+ end
48
+ end
49
+
50
+ def have_permissions(expected)
51
+ HavePermissions.new(expected)
52
+ end
53
+
54
+ class BeNoLargerThan
55
+ def initialize(width, height)
56
+ @width, @height = width, height
57
+ end
58
+
59
+ def matches?(actual)
60
+ @actual = actual
61
+ # Satisfy expectation here. Return false or raise an error if it's not met.
62
+ require 'RMagick'
63
+ img = ::Magick::Image.read(@actual.path).first
64
+ @actual_width = img.columns
65
+ @actual_height = img.rows
66
+ @actual_width <= @width && @actual_height <= @height
67
+ end
68
+
69
+ def failure_message
70
+ "expected #{@actual.inspect} to be no larger than #{@width} by #{@height}, but it was #{@actual_height} by #{@actual_width}."
71
+ end
72
+
73
+ def negative_failure_message
74
+ "expected #{@actual.inspect} to be larger than #{@width} by #{@height}, but it wasn't."
75
+ end
76
+ end
77
+
78
+ def be_no_larger_than(width, height)
79
+ BeNoLargerThan.new(width, height)
80
+ end
81
+
82
+ class HaveDimensions
83
+ def initialize(width, height)
84
+ @width, @height = width, height
85
+ end
86
+
87
+ def matches?(actual)
88
+ @actual = actual
89
+ # Satisfy expectation here. Return false or raise an error if it's not met.
90
+ require 'RMagick'
91
+ img = ::Magick::Image.read(@actual.path).first
92
+ @actual_width = img.columns
93
+ @actual_height = img.rows
94
+ @actual_width == @width && @actual_height == @height
95
+ end
96
+
97
+ def failure_message
98
+ "expected #{@actual.inspect} to have an exact size of #{@width} by #{@height}, but it was #{@actual_height} by #{@actual_width}."
99
+ end
100
+
101
+ def negative_failure_message
102
+ "expected #{@actual.inspect} not to have an exact size of #{@width} by #{@height}, but it did."
103
+ end
104
+ end
105
+
106
+ def have_dimensions(width, height)
107
+ HaveDimensions.new(width, height)
108
+ end
109
+
110
+ end # SpecHelper
111
+ end # Test
112
+ end # CarrierWave
@@ -1,37 +1,77 @@
1
1
  module CarrierWave
2
- class Uploader
3
-
4
- class << self
2
+
3
+ ##
4
+ # An uploader is a class that allows you to easily handle the caching and storage of
5
+ # uploaded files. Please refer to the README for configuration options.
6
+ #
7
+ # Once you have an uploader you can use it in isolation:
8
+ #
9
+ # my_uploader = MyUploader.new
10
+ # my_uploader.cache!(File.open(path_to_file))
11
+ # my_uploader.retrieve_from_store!('monkey.png')
12
+ #
13
+ # Alternatively, you can mount it on an ORM or other persistence layer, with
14
+ # +CarrierWave::Mount#mount_uploader+. There are extensions for activerecord and datamapper
15
+ # these are *very* simple (they are only a dozen lines of code), so adding your own should
16
+ # be trivial.
17
+ #
18
+ module Uploader
19
+
20
+ def self.append_features(base) #:nodoc:
21
+ super
22
+ base.extend(ClassMethods)
23
+ end
5
24
 
25
+ ##
26
+ # Generates a unique cache id for use in the caching system
27
+ #
28
+ # === Returns
29
+ #
30
+ # [String] a cache id in the format YYYYMMDD-HHMM-PID-RND
31
+ #
32
+ def self.generate_cache_id
33
+ Time.now.strftime('%Y%m%d-%H%M') + '-' + Process.pid.to_s + '-' + ("%04d" % rand(9999))
34
+ end
35
+
36
+ module ClassMethods
37
+
6
38
  ##
7
- # Returns a list of processor callbacks which have been declared for this uploader
39
+ # Lists processor callbacks declared
8
40
  #
9
- # @return [String]
41
+ # === Returns
42
+ #
43
+ # [Array[Array[Symbol, Array]]] a list of processor callbacks which have been declared for this uploader
10
44
  #
11
45
  def processors
12
46
  @processors ||= []
13
47
  end
14
-
48
+
15
49
  ##
16
50
  # Adds a processor callback which applies operations as a file is uploaded.
17
51
  # The argument may be the name of any method of the uploader, expressed as a symbol,
18
52
  # or a list of such methods, or a hash where the key is a method and the value is
19
53
  # an array of arguments to call the method with
20
54
  #
21
- # @param [*Symbol, Hash{Symbol => Array[]}] args
22
- # @example
23
- # class MyUploader < CarrierWave::Uploader
55
+ # === Parameters
56
+ #
57
+ # args (*Symbol, Hash{Symbol => Array[]})
58
+ #
59
+ # === Examples
60
+ #
61
+ # class MyUploader
62
+ # include CarrierWave::Uploader
63
+ #
24
64
  # process :sepiatone, :vignette
25
65
  # process :scale => [200, 200]
26
- #
66
+ #
27
67
  # def sepiatone
28
68
  # ...
29
69
  # end
30
- #
70
+ #
31
71
  # def vignette
32
72
  # ...
33
73
  # end
34
- #
74
+ #
35
75
  # def scale(height, width)
36
76
  # ...
37
77
  # end
@@ -48,7 +88,7 @@ module CarrierWave
48
88
  end
49
89
  end
50
90
  end
51
-
91
+
52
92
  ##
53
93
  # Sets the storage engine to be used when storing files with this uploader.
54
94
  # Can be any class that implements a #store!(CarrierWave::SanitizedFile) and a #retrieve!
@@ -57,14 +97,21 @@ module CarrierWave
57
97
  # to by a symbol, which should be more convenient
58
98
  #
59
99
  # 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
100
+ #
101
+ # === Parameters
102
+ #
103
+ # [storage (Symbol, Class)] The storage engine to use for this uploader
104
+ #
105
+ # === Returns
106
+ #
107
+ # [Class] the storage engine to be used with this uploader
108
+ #
109
+ # === Examples
110
+ #
64
111
  # storage :file
65
112
  # storage CarrierWave::Storage::File
66
113
  # storage MyCustomStorageEngine
67
- #
114
+ #
68
115
  def storage(storage = nil)
69
116
  if storage.is_a?(Symbol)
70
117
  @storage = get_storage_by_symbol(storage)
@@ -85,54 +132,54 @@ module CarrierWave
85
132
  end
86
133
 
87
134
  alias_method :storage=, :storage
88
-
89
- attr_accessor :version_name
135
+
136
+ def version_names
137
+ @version_names ||= []
138
+ end
90
139
 
91
140
  ##
92
141
  # Adds a new version to this uploader
93
142
  #
94
- # @param [#to_sym] name name of the version
95
- # @param [Proc] &block a block to eval on this version of the uploader
143
+ # === Parameters
144
+ #
145
+ # [name (#to_sym)] name of the version
146
+ # [&block (Proc)] a block to eval on this version of the uploader
96
147
  #
97
148
  def version(name, &block)
98
149
  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
150
+ unless versions[name]
151
+ versions[name] = Class.new(self)
152
+ versions[name].version_names.push(*version_names)
153
+ versions[name].version_names.push(name)
154
+ class_eval <<-RUBY
155
+ def #{name}
156
+ versions[:#{name}]
157
+ end
158
+ RUBY
159
+ end
160
+ versions[name].class_eval(&block) if block
161
+ versions[name]
108
162
  end
109
163
 
110
164
  ##
111
- # @return [Hash{Symbol => Class}] a list of versions available for this uploader
165
+ # === Returns
166
+ #
167
+ # [Hash{Symbol => Class}] a list of versions available for this uploader
112
168
  #
113
169
  def versions
114
170
  @versions ||= {}
115
171
  end
116
172
 
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
173
  private
127
-
174
+
128
175
  def get_storage_by_symbol(symbol)
129
- CarrierWave.config[:storage_engines][symbol]
176
+ eval(CarrierWave.config[:storage_engines][symbol])
130
177
  end
131
-
132
- end # class << self
133
-
178
+
179
+ end # ClassMethods
180
+
134
181
  attr_reader :file, :model, :mounted_as
135
-
182
+
136
183
  ##
137
184
  # If a model is given as the first parameter, it will stored in the uploader, and
138
185
  # available throught +#model+. Likewise, mounted_as stores the name of the column
@@ -142,10 +189,16 @@ module CarrierWave
142
189
  # If you do not wish to mount your uploaders with the ORM extensions in -more then you
143
190
  # can override this method inside your uploader.
144
191
  #
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
192
+ # === Parameters
193
+ #
194
+ # [model (Object)] Any kind of model object
195
+ # [mounted_as (Symbol)] The name of the column where this uploader is mounted
196
+ #
197
+ # === Examples
198
+ #
199
+ # class MyUploader
200
+ # include CarrierWave::Uploader
201
+ #
149
202
  # def store_dir
150
203
  # File.join('public', 'files', mounted_as, model.permalink)
151
204
  # end
@@ -155,14 +208,16 @@ module CarrierWave
155
208
  @model = model
156
209
  @mounted_as = mounted_as
157
210
  end
158
-
211
+
159
212
  ##
160
- # @return [Boolean] Whether the uploaded file is blank
213
+ # === Returns
214
+ #
215
+ # [Boolean] Whether the uploaded file is blank
161
216
  #
162
217
  def blank?
163
- !file or file.empty?
218
+ !file or file.blank?
164
219
  end
165
-
220
+
166
221
  ##
167
222
  # Apply all process callbacks added through CarrierWave.process
168
223
  #
@@ -171,18 +226,24 @@ module CarrierWave
171
226
  self.send(method, *args)
172
227
  end
173
228
  end
174
-
229
+
175
230
  ##
176
- # @return [String] the path where the file is currently located.
231
+ # === Returns
232
+ #
233
+ # [String] the path where the file is currently located.
177
234
  #
178
235
  def current_path
179
236
  file.path if file.respond_to?(:path)
180
237
  end
181
-
238
+
239
+ alias_method :path, :current_path
240
+
182
241
  ##
183
242
  # Returns a hash mapping the name of each version of the uploader to an instance of it
184
243
  #
185
- # @return [Hash{Symbol => CarrierWave::Uploader}] a list of uploader instances
244
+ # === Returns
245
+ #
246
+ # [Hash{Symbol => CarrierWave::Uploader}] a list of uploader instances
186
247
  #
187
248
  def versions
188
249
  return @versions if @versions
@@ -194,7 +255,9 @@ module CarrierWave
194
255
  end
195
256
 
196
257
  ##
197
- # @return [String] the location where this file is accessible via a url
258
+ # === Returns
259
+ #
260
+ # [String] the location where this file is accessible via a url
198
261
  #
199
262
  def url
200
263
  if file.respond_to?(:url) and not file.url.blank?
@@ -203,18 +266,31 @@ module CarrierWave
203
266
  File.expand_path(current_path).gsub(File.expand_path(public), '')
204
267
  end
205
268
  end
206
-
269
+
207
270
  alias_method :to_s, :url
208
-
271
+
209
272
  ##
210
273
  # Returns a string that uniquely identifies the last stored file
211
274
  #
212
- # @return [String] uniquely identifies a file
275
+ # === Returns
276
+ #
277
+ # [String] uniquely identifies a file
213
278
  #
214
279
  def identifier
215
280
  file.identifier if file.respond_to?(:identifier)
216
281
  end
217
-
282
+
283
+ ##
284
+ # Read the contents of the file
285
+ #
286
+ # === Returns
287
+ #
288
+ # [String] contents of the file
289
+ #
290
+ def read
291
+ file.read if file.respond_to?(:read)
292
+ end
293
+
218
294
  ##
219
295
  # Override this in your Uploader to change the filename.
220
296
  #
@@ -225,84 +301,126 @@ module CarrierWave
225
301
  # Do not use the version_name in the filename, as it will prevent versions from being
226
302
  # loaded correctly.
227
303
  #
228
- # @return [String] a filename
304
+ # === Returns
305
+ #
306
+ # [String] a filename
229
307
  #
230
308
  def filename
231
309
  @filename
232
310
  end
233
311
 
234
312
  ##
235
- # @return [String] the name of this version of the uploader
313
+ # === Returns
314
+ #
315
+ # [String] the name of this version of the uploader
236
316
  #
237
317
  def version_name
238
- self.class.version_name
318
+ self.class.version_names.join('_').to_sym unless self.class.version_names.blank?
239
319
  end
240
-
320
+
241
321
  ##
242
- # @return [String] the directory that is the root of the application
322
+ # === Returns
323
+ #
324
+ # [String] the directory that is the root of the application
243
325
  #
244
326
  def root
245
327
  CarrierWave.config[:root]
246
328
  end
247
-
329
+
248
330
  ##
249
- # @return [String] the directory where files will be publically accessible
331
+ # === Returns
332
+ #
333
+ # [String] the directory where files will be publically accessible
250
334
  #
251
335
  def public
252
336
  CarrierWave.config[:public]
253
337
  end
254
-
338
+
339
+ ##
340
+ # Override this method in your uploader to provide a white list of extensions which
341
+ # are allowed to be uploaded.
342
+ #
343
+ # === Returns
344
+ #
345
+ # [NilClass, Array[String]] a white list of extensions which are allowed to be uploaded
346
+ #
347
+ # === Examples
348
+ #
349
+ # def extension_white_list
350
+ # %w(jpg jpeg gif png)
351
+ # end
352
+ #
353
+ def extension_white_list; end
354
+
255
355
  ####################
256
356
  ## Cache
257
357
  ####################
258
-
358
+
259
359
  ##
260
360
  # Override this in your Uploader to change the directory where files are cached.
261
361
  #
262
- # @return [String] a directory
362
+ # === Returns
363
+ #
364
+ # [String] a directory
263
365
  #
264
366
  def cache_dir
265
367
  CarrierWave.config[:cache_dir]
266
368
  end
267
-
369
+
268
370
  ##
269
371
  # Returns a String which uniquely identifies the currently cached file for later retrieval
270
372
  #
271
- # @return [String] a cache name, in the format YYYYMMDD-HHMM-PID-RND/filename.txt
373
+ # === Returns
374
+ #
375
+ # [String] a cache name, in the format YYYYMMDD-HHMM-PID-RND/filename.txt
272
376
  #
273
377
  def cache_name
274
378
  File.join(cache_id, [version_name, original_filename].compact.join('_')) if cache_id and original_filename
275
379
  end
276
-
380
+
277
381
  ##
278
382
  # Caches the given file unless a file has already been cached, stored or retrieved.
279
383
  #
280
- # @param [File, IOString, Tempfile] new_file any kind of file object
281
- # @raise [CarrierWave::FormNotMultipart] if the assigned parameter is a string
384
+ # === Parameters
385
+ #
386
+ # [new_file (File, IOString, Tempfile)] any kind of file object
387
+ #
388
+ # === Raises
389
+ #
390
+ # [CarrierWave::FormNotMultipart] if the assigned parameter is a string
282
391
  #
283
392
  def cache(new_file)
284
393
  cache!(new_file) unless file
285
394
  end
286
-
395
+
287
396
  ##
288
397
  # Caches the given file. Calls process! to trigger any process callbacks.
289
398
  #
290
- # @param [File, IOString, Tempfile] new_file any kind of file object
291
- # @raise [CarrierWave::FormNotMultipart] if the assigned parameter is a string
399
+ # === Parameters
400
+ #
401
+ # [new_file (File, IOString, Tempfile)] any kind of file object
402
+ #
403
+ # === Raises
404
+ #
405
+ # [CarrierWave::FormNotMultipart] if the assigned parameter is a string
292
406
  #
293
407
  def cache!(new_file)
294
408
  new_file = CarrierWave::SanitizedFile.new(new_file)
295
- raise CarrierWave::FormNotMultipart if new_file.string?
409
+ raise CarrierWave::FormNotMultipart if new_file.is_path?
296
410
 
297
411
  unless new_file.empty?
412
+ if extension_white_list and not extension_white_list.include?(new_file.extension.to_s)
413
+ raise CarrierWave::IntegrityError, "You are not allowed to upload #{new_file.extension.inspect} files, allowed types: #{extension_white_list.inspect}"
414
+ end
415
+
298
416
  self.cache_id = CarrierWave::Uploader.generate_cache_id unless cache_id
299
417
 
300
418
  @file = new_file
301
419
 
302
420
  @filename = new_file.filename
303
421
  self.original_filename = new_file.filename
304
-
305
- @file = @file.copy_to(cache_path)
422
+
423
+ @file = @file.copy_to(cache_path, CarrierWave.config[:permissions])
306
424
  process!
307
425
 
308
426
  versions.each do |name, v|
@@ -311,23 +429,30 @@ module CarrierWave
311
429
  end
312
430
  end
313
431
  end
314
-
432
+
315
433
  ##
316
434
  # Retrieves the file with the given cache_name from the cache, unless a file has
317
435
  # already been cached, stored or retrieved.
318
436
  #
319
- # @param [String] cache_name uniquely identifies a cache file
437
+ # === Parameters
438
+ #
439
+ # [cache_name (String)] uniquely identifies a cache file
320
440
  #
321
441
  def retrieve_from_cache(cache_name)
322
442
  retrieve_from_cache!(cache_name) unless file
323
443
  rescue CarrierWave::InvalidParameter
324
444
  end
325
-
445
+
326
446
  ##
327
447
  # Retrieves the file with the given cache_name from the cache.
328
448
  #
329
- # @param [String] cache_name uniquely identifies a cache file
330
- # @raise [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
449
+ # === Parameters
450
+ #
451
+ # [cache_name (String)] uniquely identifies a cache file
452
+ #
453
+ # === Raises
454
+ #
455
+ # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
331
456
  #
332
457
  def retrieve_from_cache!(cache_name)
333
458
  self.cache_id, self.original_filename = cache_name.split('/', 2)
@@ -335,22 +460,40 @@ module CarrierWave
335
460
  @file = CarrierWave::SanitizedFile.new(cache_path)
336
461
  versions.each { |name, v| v.retrieve_from_cache!(cache_name) }
337
462
  end
338
-
463
+
339
464
  ####################
340
465
  ## STORE
341
466
  ####################
342
-
467
+
343
468
  ##
344
469
  # Override this in your Uploader to change the directory where the file backend stores files.
345
470
  #
346
471
  # Other backends may or may not use this method, depending on their specific needs.
347
472
  #
348
- # @return [String] a directory
473
+ # === Returns
474
+ #
475
+ # [String] a directory
349
476
  #
350
477
  def store_dir
351
- [CarrierWave.config[:store_dir], version_name].compact.join(File::Separator)
478
+ CarrierWave.config[:store_dir]
352
479
  end
353
-
480
+
481
+ ##
482
+ # Calculates the path where the file should be stored. If +for_file+ is given, it will be
483
+ # used as the filename, otherwise +CarrierWave::Uploader#filename+ is assumed.
484
+ #
485
+ # === Parameters
486
+ #
487
+ # [for_file (String)] name of the file <optional>
488
+ #
489
+ # === Returns
490
+ #
491
+ # [String] the store path
492
+ #
493
+ def store_path(for_file=filename)
494
+ File.join(store_dir, [version_name, for_file].compact.join('_'))
495
+ end
496
+
354
497
  ##
355
498
  # Stores the file by passing it to this Uploader's storage engine, unless a file has
356
499
  # already been cached, stored or retrieved.
@@ -358,18 +501,22 @@ module CarrierWave
358
501
  # If CarrierWave.config[:use_cache] is true, it will first cache the file
359
502
  # and apply any process callbacks before uploading it.
360
503
  #
361
- # @param [File, IOString, Tempfile] new_file any kind of file object
504
+ # === Parameters
505
+ #
506
+ # [new_file (File, IOString, Tempfile)] any kind of file object
362
507
  #
363
508
  def store(new_file)
364
509
  store!(new_file) unless file
365
510
  end
366
-
511
+
367
512
  ##
368
513
  # Stores the file by passing it to this Uploader's storage engine.
369
514
  #
370
515
  # If new_file is omitted, a previously cached file will be stored.
371
516
  #
372
- # @param [File, IOString, Tempfile] new_file any kind of file object
517
+ # === Parameters
518
+ #
519
+ # [new_file (File, IOString, Tempfile)] any kind of file object
373
520
  #
374
521
  def store!(new_file=nil)
375
522
  cache!(new_file) if new_file
@@ -379,49 +526,53 @@ module CarrierWave
379
526
  versions.each { |name, v| v.store!(new_file) }
380
527
  end
381
528
  end
382
-
529
+
383
530
  ##
384
531
  # Retrieves the file from the storage, unless a file has
385
532
  # already been cached, stored or retrieved.
386
- #
387
- # @param [String] identifier uniquely identifies the file to retrieve
533
+ #
534
+ # === Parameters
535
+ #
536
+ # [identifier (String)] uniquely identifies the file to retrieve
388
537
  #
389
538
  def retrieve_from_store(identifier)
390
539
  retrieve_from_store!(identifier) unless file
391
540
  rescue CarrierWave::InvalidParameter
392
541
  end
393
-
542
+
394
543
  ##
395
544
  # Retrieves the file from the storage.
396
- #
397
- # @param [String] identifier uniquely identifies the file to retrieve
545
+ #
546
+ # === Parameters
547
+ #
548
+ # [identifier (String)] uniquely identifies the file to retrieve
398
549
  #
399
550
  def retrieve_from_store!(identifier)
400
551
  @file = storage.retrieve!(self, identifier)
401
552
  versions.each { |name, v| v.retrieve_from_store!(identifier) }
402
553
  end
403
-
554
+
404
555
  private
405
-
556
+
406
557
  def cache_path
407
558
  File.expand_path(File.join(cache_dir, cache_name), public)
408
559
  end
409
-
560
+
410
561
  def storage
411
562
  self.class.storage
412
563
  end
413
-
564
+
414
565
  attr_reader :cache_id, :original_filename
415
566
 
416
567
  def cache_id=(cache_id)
417
- raise CarrierWave::InvalidParameter, "invalid cache id" unless cache_id =~ /^[\d]{8}\-[\d]{4}\-[\d]+\-[\d]{4}$/
568
+ raise CarrierWave::InvalidParameter, "invalid cache id" unless cache_id =~ /\A[\d]{8}\-[\d]{4}\-[\d]+\-[\d]{4}\z/
418
569
  @cache_id = cache_id
419
570
  end
420
-
571
+
421
572
  def original_filename=(filename)
422
- raise CarrierWave::InvalidParameter, "invalid filename" unless filename =~ /^[a-z0-9\.\-\+_]+$/i
573
+ raise CarrierWave::InvalidParameter, "invalid filename" unless filename =~ /\A[a-z0-9\.\-\+_]+\z/i
423
574
  @original_filename = filename
424
575
  end
425
-
576
+
426
577
  end # Uploader
427
- end # CarrierWave
578
+ end # CarrierWave