jnicklas-carrierwave 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,161 @@
1
+ require 'rmagick'
2
+
3
+ module CarrierWave
4
+
5
+ ##
6
+ # This module simplifies manipulation with RMagick by providing a set
7
+ # of convenient helper methods. If you want to use them, you'll need to
8
+ # require this file
9
+ #
10
+ # require 'carrierwave/processing/rmagick'
11
+ #
12
+ # And then include it in your uploader
13
+ #
14
+ # MyUploade < CarrierWave::Uploader
15
+ # include CarrierWave::RMagick
16
+ # end
17
+ #
18
+ # You can now use the provided helpers:
19
+ #
20
+ # MyUploade < CarrierWave::Uploader
21
+ # include CarrierWave::RMagick
22
+ #
23
+ # process :resize_to_fit => [200, 200]
24
+ # end
25
+ #
26
+ # Or create your own helpers with the powerful manipulate! method. Check
27
+ # out the RMagick docs at http://www.imagemagick.org/RMagick/doc/ for more
28
+ # info
29
+ #
30
+ # MyUploade < CarrierWave::Uploader
31
+ # include CarrierWave::RMagick
32
+ #
33
+ # process :do_stuff => 10.0
34
+ #
35
+ # def do_stuff(blur_factor)
36
+ # manipulate! do |img|
37
+ # img = img.sepiatone
38
+ # img = img.auto_orient
39
+ # img = img.radial_blur(blur_factor)
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ module RMagick
45
+
46
+ ##
47
+ # Changes the image encoding format to the given format
48
+ #
49
+ # @see http://www.imagemagick.org/RMagick/doc/magick.html#formats
50
+ # @param [#to_s] format an abreviation of the format
51
+ # @yieldparam [Magick::Image] img additional manipulations to perform
52
+ # @example
53
+ # image.convert(:png)
54
+ #
55
+ def convert(format)
56
+ manipulate! do |img|
57
+ img.format = format.to_s.upcase
58
+ img = yield(img) if block_given?
59
+ img
60
+ end
61
+ end
62
+
63
+ ##
64
+ # From the RMagick documentation: "Resize the image to fit within the
65
+ # specified dimensions while retaining the original aspect ratio. The
66
+ # image may be shorter or narrower than specified in the smaller dimension
67
+ # but will not be larger than the specified values."
68
+ #
69
+ # @see http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fit
70
+ #
71
+ # @param [Integer] width the width to scale the image to
72
+ # @param [Integer] height the height to scale the image to
73
+ # @yieldparam [Magick::Image] img additional manipulations to perform
74
+ #
75
+ def resize_to_fit(width, height)
76
+ manipulate! do |img|
77
+ img.resize_to_fit!(width, height)
78
+ img = yield(img) if block_given?
79
+ img
80
+ end
81
+ end
82
+
83
+ alias_method :resize, :resize_to_fit
84
+
85
+ ##
86
+ # From the RMagick documentation: "Resize the image to fit within the
87
+ # specified dimensions while retaining the aspect ratio of the original
88
+ # image. If necessary, crop the image in the larger dimension."
89
+ #
90
+ # @see http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fill
91
+ #
92
+ # @param [Integer] width the width to scale the image to
93
+ # @param [Integer] height the height to scale the image to
94
+ # @yieldparam [Magick::Image] img additional manipulations to perform
95
+ #
96
+ def resize_to_fill(width, height)
97
+ manipulate! do |img|
98
+ img.resize_to_fill!(width, height)
99
+ img = yield(img) if block_given?
100
+ img
101
+ end
102
+ end
103
+
104
+ alias_method :crop_resized, :resize_to_fill
105
+
106
+ ##
107
+ # Resize the image to fit within the specified dimensions while retaining
108
+ # the original aspect ratio. If necessary will pad the remaining area
109
+ # with the given color, which defaults to transparent (for gif and png,
110
+ # white for jpeg).
111
+ #
112
+ # @param [Integer] width the width to scale the image to
113
+ # @param [Integer] height the height to scale the image to
114
+ # @param [String, :transparent] background the color of the background as a hexcode, like "#ff45de"
115
+ # @param [Magick::GravityType] gravity how to position the image
116
+ # @yieldparam [Magick::Image] img additional manipulations to perform
117
+ #
118
+ def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
119
+ manipulate! do |img|
120
+ img.resize_to_fit!(width, height)
121
+ new_img = ::Magick::Image.new(width, height)
122
+ if background == :transparent
123
+ new_img = new_img.matte_floodfill(1, 1)
124
+ else
125
+ new_img = new_img.color_floodfill(1, 1, ::Magick::Pixel.from_color(background))
126
+ end
127
+ new_img = new_img.composite(img, gravity, ::Magick::OverCompositeOp)
128
+ new_img = yield(new_img) if block_given?
129
+ new_img
130
+ end
131
+ end
132
+
133
+ ##
134
+ # Manipulate the image with RMagick. This method will load up an image
135
+ # and then pass each of its frames to the supplied block. It will then
136
+ # save the image to disk.
137
+ #
138
+ # Note: This method assumes that the object responds to current_path
139
+ # any class that this is mixed into must have a current_path method.
140
+ #
141
+ # @yieldparam [Magick::Image] img manipulations to perform
142
+ # @raise [CarrierWave::ProcessingError] if manipulation failed.
143
+ #
144
+ def manipulate!
145
+ image = ::Magick::Image.read(current_path)
146
+
147
+ if image.size > 1
148
+ list = ::Magick::ImageList.new
149
+ image.each do |frame|
150
+ list << yield( frame )
151
+ end
152
+ list.write(current_path)
153
+ else
154
+ yield( image.first ).write(current_path)
155
+ end
156
+ rescue ::Magick::ImageMagickError => e
157
+ raise CarrierWave::ProcessingError.new("Failed to manipulate with rmagick, maybe it is not an image? Original Error: #{e}")
158
+ end
159
+
160
+ end # RMagick
161
+ end # CarrierWave
@@ -0,0 +1,231 @@
1
+ module CarrierWave
2
+ class SanitizedFile
3
+
4
+ attr_accessor :file, :options
5
+
6
+ def initialize(file, options = {})
7
+ self.file = file
8
+ self.options = options
9
+ end
10
+
11
+ ##
12
+ # Returns the filename as is, without sanizting it.
13
+ #
14
+ # @return [String] the unsanitized filename
15
+ #
16
+ def original_filename
17
+ return @original_filename if @original_filename
18
+ if @file and @file.respond_to?(:original_filename)
19
+ @file.original_filename
20
+ elsif path
21
+ File.basename(path)
22
+ end
23
+ end
24
+
25
+ ##
26
+ # Returns the filename, sanitized to strip out any evil characters.
27
+ #
28
+ # @return [String] the sanitized filename
29
+ #
30
+ def filename
31
+ sanitize(original_filename) if original_filename
32
+ end
33
+
34
+ alias_method :identifier, :filename
35
+
36
+ ##
37
+ # Returns the part of the filename before the extension. So if a file is called 'test.jpeg'
38
+ # this would return 'test'
39
+ #
40
+ # @return [String] the first part of the filename
41
+ #
42
+ def basename
43
+ split_extension(filename)[0] if filename
44
+ end
45
+
46
+ ##
47
+ # Returns the file extension
48
+ #
49
+ # @return [String] the extension
50
+ #
51
+ def extension
52
+ split_extension(filename)[1] if filename
53
+ end
54
+
55
+ ##
56
+ # Returns the file's size.
57
+ #
58
+ # @return [Integer] the file's size in bytes.
59
+ #
60
+ def size
61
+ if @file.respond_to?(:size)
62
+ @file.size
63
+ elsif path
64
+ File.size(path)
65
+ else
66
+ 0
67
+ end
68
+ end
69
+
70
+ ##
71
+ # Returns the full path to the file. If the file has no path, it will return nil.
72
+ #
73
+ # @return [String, nil] the path where the file is located.
74
+ #
75
+ def path
76
+ unless @file.blank?
77
+ if string?
78
+ File.expand_path(@file)
79
+ elsif @file.respond_to?(:path) and not @file.path.blank?
80
+ File.expand_path(@file.path)
81
+ end
82
+ end
83
+ end
84
+
85
+ ##
86
+ # Returns true if the file is supplied as a pathname or as a string.
87
+ #
88
+ # @return [Boolean]
89
+ #
90
+ def string?
91
+ !!((@file.is_a?(String) || @file.is_a?(Pathname)) && !@file.blank?)
92
+ end
93
+
94
+ ##
95
+ # Checks if the file is valid and has a non-zero size
96
+ #
97
+ # @return [Boolean]
98
+ #
99
+ def empty?
100
+ (@file.nil? && @path.nil?) || self.size.nil? || self.size.zero?
101
+ end
102
+
103
+ ##
104
+ # Checks if the file exists
105
+ #
106
+ # @return [Boolean]
107
+ #
108
+ def exists?
109
+ return File.exists?(self.path) if self.path
110
+ return false
111
+ end
112
+
113
+ ##
114
+ # Returns the contents of the file.
115
+ #
116
+ # @return [String] contents of the file
117
+ #
118
+ def read
119
+ if string?
120
+ File.read(@file)
121
+ else
122
+ @file.rewind if @file.respond_to?(:rewind)
123
+ @file.read
124
+ end
125
+ end
126
+
127
+ ##
128
+ # Moves the file to the given path
129
+ #
130
+ # @param [String] new_path The path where the file should be moved.
131
+ #
132
+ def move_to(new_path)
133
+ return if self.empty?
134
+ new_path = File.expand_path(new_path)
135
+
136
+ mkdir!(new_path)
137
+ if exists?
138
+ FileUtils.mv(path, new_path) unless new_path == path
139
+ else
140
+ File.open(new_path, "wb") { |f| f.write(read) }
141
+ end
142
+ chmod!(new_path)
143
+ self.file = new_path
144
+ end
145
+
146
+ ##
147
+ # Creates a copy of this file and moves it to the given path. Returns the copy.
148
+ #
149
+ # @param [String] new_path The path where the file should be copied to.
150
+ # @return [CarrierWave::SanitizedFile] the location where the file will be stored.
151
+ #
152
+ def copy_to(new_path)
153
+ return if self.empty?
154
+ new_path = File.expand_path(new_path)
155
+
156
+ mkdir!(new_path)
157
+ if exists?
158
+ FileUtils.cp(path, new_path) unless new_path == path
159
+ else
160
+ File.open(new_path, "wb") { |f| f.write(read) }
161
+ end
162
+ chmod!(new_path)
163
+ self.class.new(new_path)
164
+ end
165
+
166
+ ##
167
+ # Removes the file from the filesystem.
168
+ #
169
+ def delete
170
+ FileUtils.rm(self.path) if exists?
171
+ end
172
+
173
+ ##
174
+ # Returns the content type of the file.
175
+ #
176
+ # @return [String] the content type of the file
177
+ #
178
+ def content_type
179
+ return @content_type if @content_type
180
+ @file.content_type.chomp if @file.respond_to?(:content_type) and @file.content_type
181
+ end
182
+
183
+ private
184
+
185
+ def file=(file)
186
+ if file.is_a?(Hash)
187
+ @file = file["tempfile"]
188
+ @original_filename = file["filename"]
189
+ @content_type = file["content_type"]
190
+ else
191
+ @file = file
192
+ @original_filename = nil
193
+ @content_type = nil
194
+ end
195
+ end
196
+
197
+ # create the directory if it doesn't exist
198
+ def mkdir!(path)
199
+ FileUtils.mkdir_p(File.dirname(path)) unless File.exists?(File.dirname(path))
200
+ end
201
+
202
+ def chmod!(path)
203
+ File.chmod(@options[:permissions], path) if @options[:permissions]
204
+ end
205
+
206
+ # Sanitize the filename, to prevent hacking
207
+ def sanitize(name)
208
+ name = name.gsub("\\", "/") # work-around for IE
209
+ name = File.basename(name)
210
+ name = name.gsub(/[^a-zA-Z0-9\.\-\+_]/,"_")
211
+ name = "_#{name}" if name =~ /^\.+$/
212
+ name = "unnamed" if name.size == 0
213
+ return name.downcase
214
+ end
215
+
216
+ def split_extension(fn)
217
+ # regular expressions to try for identifying extensions
218
+ ext_regexps = [
219
+ /^(.+)\.([^\.]{1,3}\.[^\.]{1,4})$/, # matches "something.tar.gz"
220
+ /^(.+)\.([^\.]+)$/ # matches "something.jpg"
221
+ ]
222
+ ext_regexps.each do |regexp|
223
+ if fn =~ regexp
224
+ return $1, $2
225
+ end
226
+ end
227
+ return fn, "" # In case we weren't able to split the extension
228
+ end
229
+
230
+ end # SanitizedFile
231
+ end # CarrierWave
@@ -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.root)
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.root)
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