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.
- data/LICENSE +1 -1
- data/{README.md → README.rdoc} +73 -78
- data/Rakefile +12 -6
- data/lib/carrierwave.rb +33 -17
- data/lib/carrierwave/mount.rb +133 -54
- data/lib/carrierwave/orm/activerecord.rb +53 -2
- data/lib/carrierwave/orm/datamapper.rb +7 -2
- data/lib/carrierwave/orm/sequel.rb +23 -0
- data/lib/carrierwave/processing/image_science.rb +3 -3
- data/lib/carrierwave/processing/rmagick.rb +103 -47
- data/lib/carrierwave/sanitized_file.rb +74 -48
- data/lib/carrierwave/storage/abstract.rb +24 -9
- data/lib/carrierwave/storage/file.rb +23 -9
- data/lib/carrierwave/storage/s3.rb +67 -16
- data/lib/carrierwave/test/matchers.rb +112 -0
- data/lib/carrierwave/uploader.rb +264 -113
- data/lib/generators/uploader_generator.rb +7 -7
- data/rails_generators/uploader/templates/uploader.rb +9 -1
- data/spec/mount_spec.rb +147 -3
- data/spec/orm/activerecord_spec.rb +24 -3
- data/spec/orm/datamapper_spec.rb +3 -3
- data/spec/orm/sequel_spec.rb +170 -0
- data/spec/sanitized_file_spec.rb +22 -36
- data/spec/spec_helper.rb +44 -80
- data/spec/uploader_spec.rb +107 -22
- metadata +8 -4
@@ -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
|
-
#
|
30
|
-
#
|
30
|
+
# === Parameters
|
31
|
+
#
|
32
|
+
# [uploader (CarrierWave::Uploader)] an uploader object
|
33
|
+
# [file (CarrierWave::SanitizedFile)] the file to store
|
31
34
|
#
|
32
|
-
#
|
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
|
-
#
|
41
|
-
#
|
45
|
+
# === Parameters
|
46
|
+
#
|
47
|
+
# [uploader (CarrierWave::Uploader)] an uploader object
|
48
|
+
# [identifier (String)] uniquely identifies the file
|
42
49
|
#
|
43
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
13
|
-
# @param [CarrierWave::SanitizedFile] file the file to store
|
18
|
+
# === Parameters
|
14
19
|
#
|
15
|
-
#
|
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.
|
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
|
-
#
|
28
|
-
#
|
37
|
+
# === Parameters
|
38
|
+
#
|
39
|
+
# [uploader (CarrierWave::Uploader)] an uploader object
|
40
|
+
# [identifier (String)] the filename of the file
|
41
|
+
#
|
42
|
+
# === Returns
|
29
43
|
#
|
30
|
-
#
|
44
|
+
# [CarrierWave::SanitizedFile] a sanitized file
|
31
45
|
#
|
32
46
|
def self.retrieve!(uploader, identifier)
|
33
|
-
path = ::File.join(uploader.
|
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(
|
9
|
-
@
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
43
|
-
#
|
69
|
+
# === Parameters
|
70
|
+
#
|
71
|
+
# [uploader (CarrierWave::Uploader)] an uploader object
|
72
|
+
# [file (CarrierWave::SanitizedFile)] the file to store
|
44
73
|
#
|
45
|
-
#
|
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.
|
49
|
-
self.new(
|
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
|
-
#
|
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(
|
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
|
-
#
|
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
|
-
#
|
124
|
+
# === Returns
|
125
|
+
#
|
126
|
+
# [String] file's url
|
76
127
|
#
|
77
128
|
def url
|
78
|
-
"http://s3.amazonaws.com
|
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
|
data/lib/carrierwave/uploader.rb
CHANGED
@@ -1,37 +1,77 @@
|
|
1
1
|
module CarrierWave
|
2
|
-
|
3
|
-
|
4
|
-
|
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
|
-
#
|
39
|
+
# Lists processor callbacks declared
|
8
40
|
#
|
9
|
-
#
|
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
|
-
#
|
22
|
-
#
|
23
|
-
#
|
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
|
-
#
|
62
|
-
#
|
63
|
-
#
|
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
|
-
|
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
|
-
#
|
95
|
-
#
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
#
|
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 #
|
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
|
-
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
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
|
-
#
|
213
|
+
# === Returns
|
214
|
+
#
|
215
|
+
# [Boolean] Whether the uploaded file is blank
|
161
216
|
#
|
162
217
|
def blank?
|
163
|
-
!file or file.
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
304
|
+
# === Returns
|
305
|
+
#
|
306
|
+
# [String] a filename
|
229
307
|
#
|
230
308
|
def filename
|
231
309
|
@filename
|
232
310
|
end
|
233
311
|
|
234
312
|
##
|
235
|
-
#
|
313
|
+
# === Returns
|
314
|
+
#
|
315
|
+
# [String] the name of this version of the uploader
|
236
316
|
#
|
237
317
|
def version_name
|
238
|
-
self.class.
|
318
|
+
self.class.version_names.join('_').to_sym unless self.class.version_names.blank?
|
239
319
|
end
|
240
|
-
|
320
|
+
|
241
321
|
##
|
242
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
281
|
-
#
|
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
|
-
#
|
291
|
-
#
|
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.
|
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
|
-
#
|
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
|
-
#
|
330
|
-
#
|
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
|
-
#
|
473
|
+
# === Returns
|
474
|
+
#
|
475
|
+
# [String] a directory
|
349
476
|
#
|
350
477
|
def store_dir
|
351
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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 =~
|
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 =~
|
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
|