jnicklas-carrierwave 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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