carrierwave-rails3 0.4.5

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.
Files changed (67) hide show
  1. data/README.rdoc +527 -0
  2. data/lib/carrierwave.rb +103 -0
  3. data/lib/carrierwave/compatibility/paperclip.rb +95 -0
  4. data/lib/carrierwave/core_ext/file.rb +11 -0
  5. data/lib/carrierwave/mount.rb +359 -0
  6. data/lib/carrierwave/orm/activerecord.rb +75 -0
  7. data/lib/carrierwave/orm/datamapper.rb +27 -0
  8. data/lib/carrierwave/orm/mongoid.rb +23 -0
  9. data/lib/carrierwave/orm/mongomapper.rb +27 -0
  10. data/lib/carrierwave/orm/sequel.rb +45 -0
  11. data/lib/carrierwave/processing/image_science.rb +116 -0
  12. data/lib/carrierwave/processing/mini_magick.rb +261 -0
  13. data/lib/carrierwave/processing/rmagick.rb +278 -0
  14. data/lib/carrierwave/sanitized_file.rb +273 -0
  15. data/lib/carrierwave/storage/abstract.rb +30 -0
  16. data/lib/carrierwave/storage/cloud_files.rb +169 -0
  17. data/lib/carrierwave/storage/file.rb +48 -0
  18. data/lib/carrierwave/storage/grid_fs.rb +104 -0
  19. data/lib/carrierwave/storage/right_s3.rb +3 -0
  20. data/lib/carrierwave/storage/s3.rb +206 -0
  21. data/lib/carrierwave/test/matchers.rb +164 -0
  22. data/lib/carrierwave/uploader.rb +44 -0
  23. data/lib/carrierwave/uploader/cache.rb +146 -0
  24. data/lib/carrierwave/uploader/callbacks.rb +41 -0
  25. data/lib/carrierwave/uploader/configuration.rb +134 -0
  26. data/lib/carrierwave/uploader/default_url.rb +19 -0
  27. data/lib/carrierwave/uploader/download.rb +60 -0
  28. data/lib/carrierwave/uploader/extension_whitelist.rb +38 -0
  29. data/lib/carrierwave/uploader/mountable.rb +39 -0
  30. data/lib/carrierwave/uploader/processing.rb +84 -0
  31. data/lib/carrierwave/uploader/proxy.rb +62 -0
  32. data/lib/carrierwave/uploader/remove.rb +23 -0
  33. data/lib/carrierwave/uploader/store.rb +90 -0
  34. data/lib/carrierwave/uploader/url.rb +33 -0
  35. data/lib/carrierwave/uploader/versions.rb +147 -0
  36. data/lib/generators/templates/uploader.rb +47 -0
  37. data/lib/generators/uploader_generator.rb +13 -0
  38. data/spec/compatibility/paperclip_spec.rb +52 -0
  39. data/spec/mount_spec.rb +538 -0
  40. data/spec/orm/activerecord_spec.rb +271 -0
  41. data/spec/orm/datamapper_spec.rb +168 -0
  42. data/spec/orm/mongoid_spec.rb +202 -0
  43. data/spec/orm/mongomapper_spec.rb +202 -0
  44. data/spec/orm/sequel_spec.rb +183 -0
  45. data/spec/processing/image_science_spec.rb +56 -0
  46. data/spec/processing/mini_magick_spec.rb +76 -0
  47. data/spec/processing/rmagick_spec.rb +75 -0
  48. data/spec/sanitized_file_spec.rb +623 -0
  49. data/spec/spec_helper.rb +92 -0
  50. data/spec/storage/cloudfiles_spec.rb +78 -0
  51. data/spec/storage/grid_fs_spec.rb +86 -0
  52. data/spec/storage/s3_spec.rb +118 -0
  53. data/spec/uploader/cache_spec.rb +209 -0
  54. data/spec/uploader/callback_spec.rb +24 -0
  55. data/spec/uploader/configuration_spec.rb +105 -0
  56. data/spec/uploader/default_url_spec.rb +85 -0
  57. data/spec/uploader/download_spec.rb +75 -0
  58. data/spec/uploader/extension_whitelist_spec.rb +44 -0
  59. data/spec/uploader/mountable_spec.rb +33 -0
  60. data/spec/uploader/paths_spec.rb +22 -0
  61. data/spec/uploader/processing_spec.rb +73 -0
  62. data/spec/uploader/proxy_spec.rb +54 -0
  63. data/spec/uploader/remove_spec.rb +70 -0
  64. data/spec/uploader/store_spec.rb +264 -0
  65. data/spec/uploader/url_spec.rb +102 -0
  66. data/spec/uploader/versions_spec.rb +298 -0
  67. metadata +128 -0
@@ -0,0 +1,278 @@
1
+ # encoding: utf-8
2
+
3
+ unless defined? Magick
4
+ begin
5
+ require 'rmagick'
6
+ rescue LoadError
7
+ require 'RMagick'
8
+ rescue LoadError
9
+ puts "WARNING: Failed to require rmagick, image processing may fail!"
10
+ end
11
+ end
12
+
13
+ module CarrierWave
14
+
15
+ ##
16
+ # This module simplifies manipulation with RMagick by providing a set
17
+ # of convenient helper methods. If you want to use them, you'll need to
18
+ # require this file:
19
+ #
20
+ # require 'carrierwave/processing/rmagick'
21
+ #
22
+ # And then include it in your uploader:
23
+ #
24
+ # class MyUploader < CarrierWave::Uploader::Base
25
+ # include CarrierWave::RMagick
26
+ # end
27
+ #
28
+ # You can now use the provided helpers:
29
+ #
30
+ # class MyUploader < CarrierWave::Uploader::Base
31
+ # include CarrierWave::RMagick
32
+ #
33
+ # process :resize_to_fit => [200, 200]
34
+ # end
35
+ #
36
+ # Or create your own helpers with the powerful manipulate! method. Check
37
+ # out the RMagick docs at http://www.imagemagick.org/RMagick/doc/ for more
38
+ # info
39
+ #
40
+ # class MyUploader < CarrierWave::Uploader::Base
41
+ # include CarrierWave::RMagick
42
+ #
43
+ # process :do_stuff => 10.0
44
+ #
45
+ # def do_stuff(blur_factor)
46
+ # manipulate! do |img|
47
+ # img = img.sepiatone
48
+ # img = img.auto_orient
49
+ # img = img.radial_blur(blur_factor)
50
+ # end
51
+ # end
52
+ # end
53
+ #
54
+ # === Note
55
+ #
56
+ # You should be aware how RMagick handles memory. manipulate! takes care
57
+ # of freeing up memory for you, but for optimum memory usage you should
58
+ # use destructive operations as much as possible:
59
+ #
60
+ # DON'T DO THIS:
61
+ # img = img.resize_to_fit
62
+ #
63
+ # DO THIS INSTEAD:
64
+ # img.resize_to_fit!
65
+ #
66
+ # Read this for more information why:
67
+ #
68
+ # http://rubyforge.org/forum/forum.php?thread_id=1374&forum_id=1618
69
+ #
70
+ module RMagick
71
+ extend ActiveSupport::Concern
72
+
73
+ module ClassMethods
74
+ def convert(format)
75
+ process :convert => format
76
+ end
77
+
78
+ def resize_to_limit(width, height)
79
+ process :resize_to_limit => [width, height]
80
+ end
81
+
82
+ def resize_to_fit(width, height)
83
+ process :resize_to_fit => [width, height]
84
+ end
85
+
86
+ def resize_to_fill(width, height, gravity=::Magick::CenterGravity)
87
+ process :resize_to_fill => [width, height, gravity]
88
+ end
89
+
90
+ def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
91
+ process :resize_and_pad => [width, height, background, gravity]
92
+ end
93
+ end
94
+
95
+ ##
96
+ # Changes the image encoding format to the given format
97
+ #
98
+ # See even http://www.imagemagick.org/RMagick/doc/magick.html#formats
99
+ #
100
+ # === Parameters
101
+ #
102
+ # [format (#to_s)] an abreviation of the format
103
+ #
104
+ # === Yields
105
+ #
106
+ # [Magick::Image] additional manipulations to perform
107
+ #
108
+ # === Examples
109
+ #
110
+ # image.convert(:png)
111
+ #
112
+ def convert(format)
113
+ manipulate!(:format => format)
114
+ end
115
+
116
+ ##
117
+ # Resize the image to fit within the specified dimensions while retaining
118
+ # the original aspect ratio. Will only resize the image if it is larger than the
119
+ # specified dimensions. The resulting image may be shorter or narrower than specified
120
+ # in the smaller dimension but will not be larger than the specified values.
121
+ #
122
+ # === Parameters
123
+ #
124
+ # [width (Integer)] the width to scale the image to
125
+ # [height (Integer)] the height to scale the image to
126
+ #
127
+ # === Yields
128
+ #
129
+ # [Magick::Image] additional manipulations to perform
130
+ #
131
+ def resize_to_limit(width, height)
132
+ manipulate! do |img|
133
+ geometry = Magick::Geometry.new(width, height, 0, 0, Magick::GreaterGeometry)
134
+ new_img = img.change_geometry(geometry) do |new_width, new_height|
135
+ img.resize(new_width, new_height)
136
+ end
137
+ destroy_image(img)
138
+ new_img = yield(new_img) if block_given?
139
+ new_img
140
+ end
141
+ end
142
+
143
+ ##
144
+ # From the RMagick documentation: "Resize the image to fit within the
145
+ # specified dimensions while retaining the original aspect ratio. The
146
+ # image may be shorter or narrower than specified in the smaller dimension
147
+ # but will not be larger than the specified values."
148
+ #
149
+ # See even http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fit
150
+ #
151
+ # === Parameters
152
+ #
153
+ # [width (Integer)] the width to scale the image to
154
+ # [height (Integer)] the height to scale the image to
155
+ #
156
+ # === Yields
157
+ #
158
+ # [Magick::Image] additional manipulations to perform
159
+ #
160
+ def resize_to_fit(width, height)
161
+ manipulate! do |img|
162
+ img.resize_to_fit!(width, height)
163
+ img = yield(img) if block_given?
164
+ img
165
+ end
166
+ end
167
+
168
+ ##
169
+ # From the RMagick documentation: "Resize the image to fit within the
170
+ # specified dimensions while retaining the aspect ratio of the original
171
+ # image. If necessary, crop the image in the larger dimension."
172
+ #
173
+ # See even http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fill
174
+ #
175
+ # === Parameters
176
+ #
177
+ # [width (Integer)] the width to scale the image to
178
+ # [height (Integer)] the height to scale the image to
179
+ #
180
+ # === Yields
181
+ #
182
+ # [Magick::Image] additional manipulations to perform
183
+ #
184
+ def resize_to_fill(width, height, gravity=::Magick::CenterGravity)
185
+ manipulate! do |img|
186
+ img.crop_resized!(width, height, gravity)
187
+ img = yield(img) if block_given?
188
+ img
189
+ end
190
+ end
191
+
192
+ ##
193
+ # Resize the image to fit within the specified dimensions while retaining
194
+ # the original aspect ratio. If necessary, will pad the remaining area
195
+ # with the given color, which defaults to transparent (for gif and png,
196
+ # white for jpeg).
197
+ #
198
+ # === Parameters
199
+ #
200
+ # [width (Integer)] the width to scale the image to
201
+ # [height (Integer)] the height to scale the image to
202
+ # [background (String, :transparent)] the color of the background as a hexcode, like "#ff45de"
203
+ # [gravity (Magick::GravityType)] how to position the image
204
+ #
205
+ # === Yields
206
+ #
207
+ # [Magick::Image] additional manipulations to perform
208
+ #
209
+ def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
210
+ manipulate! do |img|
211
+ img.resize_to_fit!(width, height)
212
+ new_img = ::Magick::Image.new(width, height)
213
+ if background == :transparent
214
+ filled = new_img.matte_floodfill(1, 1)
215
+ else
216
+ filled = new_img.color_floodfill(1, 1, ::Magick::Pixel.from_color(background))
217
+ end
218
+ destroy_image(new_img)
219
+ filled.composite!(img, gravity, ::Magick::OverCompositeOp)
220
+ destroy_image(img)
221
+ filled = yield(filled) if block_given?
222
+ filled
223
+ end
224
+ end
225
+
226
+ ##
227
+ # Manipulate the image with RMagick. This method will load up an image
228
+ # and then pass each of its frames to the supplied block. It will then
229
+ # save the image to disk.
230
+ #
231
+ # === Gotcha
232
+ #
233
+ # This method assumes that the object responds to +current_path+.
234
+ # Any class that this module is mixed into must have a +current_path+ method.
235
+ # CarrierWave::Uploader does, so you won't need to worry about this in
236
+ # most cases.
237
+ #
238
+ # === Yields
239
+ #
240
+ # [Magick::Image] manipulations to perform
241
+ #
242
+ # === Raises
243
+ #
244
+ # [CarrierWave::ProcessingError] if manipulation failed.
245
+ #
246
+ def manipulate!(options={})
247
+ image = ::Magick::Image.read(current_path)
248
+
249
+ frames = if image.size > 1
250
+ list = ::Magick::ImageList.new
251
+ image.each do |frame|
252
+ list << yield( frame )
253
+ end
254
+ list
255
+ else
256
+ frame = image.first
257
+ frame = yield( frame ) if block_given?
258
+ frame
259
+ end
260
+
261
+ if options[:format]
262
+ frames.write("#{options[:format]}:#{current_path}")
263
+ else
264
+ frames.write(current_path)
265
+ end
266
+ destroy_image(frames)
267
+ rescue ::Magick::ImageMagickError => e
268
+ raise CarrierWave::ProcessingError.new("Failed to manipulate with rmagick, maybe it is not an image? Original Error: #{e}")
269
+ end
270
+
271
+ private
272
+
273
+ def destroy_image(image)
274
+ image.destroy! if image.respond_to?(:destroy!)
275
+ end
276
+
277
+ end # RMagick
278
+ end # CarrierWave
@@ -0,0 +1,273 @@
1
+ # encoding: utf-8
2
+
3
+ require 'pathname'
4
+
5
+ module CarrierWave
6
+
7
+ ##
8
+ # SanitizedFile is a base class which provides a common API around all
9
+ # the different quirky Ruby File libraries. It has support for Tempfile,
10
+ # File, StringIO, Merb-style upload Hashes, as well as paths given as
11
+ # Strings and Pathnames.
12
+ #
13
+ # It's probably needlessly comprehensive and complex. Help is appreciated.
14
+ #
15
+ class SanitizedFile
16
+
17
+ attr_accessor :file
18
+
19
+ def initialize(file)
20
+ self.file = file
21
+ end
22
+
23
+ ##
24
+ # Returns the filename as is, without sanizting it.
25
+ #
26
+ # === Returns
27
+ #
28
+ # [String] the unsanitized filename
29
+ #
30
+ def original_filename
31
+ return @original_filename if @original_filename
32
+ if @file and @file.respond_to?(:original_filename)
33
+ @file.original_filename
34
+ elsif path
35
+ File.basename(path)
36
+ end
37
+ end
38
+
39
+ ##
40
+ # Returns the filename, sanitized to strip out any evil characters.
41
+ #
42
+ # === Returns
43
+ #
44
+ # [String] the sanitized filename
45
+ #
46
+ def filename
47
+ sanitize(original_filename) if original_filename
48
+ end
49
+
50
+ alias_method :identifier, :filename
51
+
52
+ ##
53
+ # Returns the part of the filename before the extension. So if a file is called 'test.jpeg'
54
+ # this would return 'test'
55
+ #
56
+ # === Returns
57
+ #
58
+ # [String] the first part of the filename
59
+ #
60
+ def basename
61
+ split_extension(filename)[0] if filename
62
+ end
63
+
64
+ ##
65
+ # Returns the file extension
66
+ #
67
+ # === Returns
68
+ #
69
+ # [String] the extension
70
+ #
71
+ def extension
72
+ split_extension(filename)[1] if filename
73
+ end
74
+
75
+ ##
76
+ # Returns the file's size.
77
+ #
78
+ # === Returns
79
+ #
80
+ # [Integer] the file's size in bytes.
81
+ #
82
+ def size
83
+ if is_path?
84
+ exists? ? File.size(path) : 0
85
+ elsif @file.respond_to?(:size)
86
+ @file.size
87
+ elsif path
88
+ exists? ? File.size(path) : 0
89
+ else
90
+ 0
91
+ end
92
+ end
93
+
94
+ ##
95
+ # Returns the full path to the file. If the file has no path, it will return nil.
96
+ #
97
+ # === Returns
98
+ #
99
+ # [String, nil] the path where the file is located.
100
+ #
101
+ def path
102
+ unless @file.blank?
103
+ if is_path?
104
+ File.expand_path(@file)
105
+ elsif @file.respond_to?(:path) and not @file.path.blank?
106
+ File.expand_path(@file.path)
107
+ end
108
+ end
109
+ end
110
+
111
+ ##
112
+ # === Returns
113
+ #
114
+ # [Boolean] whether the file is supplied as a pathname or string.
115
+ #
116
+ def is_path?
117
+ !!((@file.is_a?(String) || @file.is_a?(Pathname)) && !@file.blank?)
118
+ end
119
+
120
+ ##
121
+ # === Returns
122
+ #
123
+ # [Boolean] whether the file is valid and has a non-zero size
124
+ #
125
+ def empty?
126
+ @file.nil? || self.size.nil? || self.size.zero?
127
+ end
128
+
129
+ alias_method :blank?, :empty?
130
+
131
+ ##
132
+ # === Returns
133
+ #
134
+ # [Boolean] Whether the file exists
135
+ #
136
+ def exists?
137
+ return File.exists?(self.path) if self.path
138
+ return false
139
+ end
140
+
141
+ ##
142
+ # Returns the contents of the file.
143
+ #
144
+ # === Returns
145
+ #
146
+ # [String] contents of the file
147
+ #
148
+ def read
149
+ if is_path?
150
+ File.open(@file, "rb").read
151
+ else
152
+ @file.rewind if @file.respond_to?(:rewind)
153
+ @file.read
154
+ end
155
+ end
156
+
157
+ ##
158
+ # Moves the file to the given path
159
+ #
160
+ # === Parameters
161
+ #
162
+ # [new_path (String)] The path where the file should be moved.
163
+ # [permissions (Integer)] permissions to set on the file in its new location.
164
+ #
165
+ def move_to(new_path, permissions=nil)
166
+ return if self.empty?
167
+ new_path = File.expand_path(new_path)
168
+
169
+ mkdir!(new_path)
170
+ if exists?
171
+ FileUtils.mv(path, new_path) unless new_path == path
172
+ else
173
+ File.open(new_path, "wb") { |f| f.write(read) }
174
+ end
175
+ chmod!(new_path, permissions)
176
+ self.file = new_path
177
+ end
178
+
179
+ ##
180
+ # Creates a copy of this file and moves it to the given path. Returns the copy.
181
+ #
182
+ # === Parameters
183
+ #
184
+ # [new_path (String)] The path where the file should be copied to.
185
+ # [permissions (Integer)] permissions to set on the copy
186
+ #
187
+ # === Returns
188
+ #
189
+ # @return [CarrierWave::SanitizedFile] the location where the file will be stored.
190
+ #
191
+ def copy_to(new_path, permissions=nil)
192
+ return if self.empty?
193
+ new_path = File.expand_path(new_path)
194
+
195
+ mkdir!(new_path)
196
+ if exists?
197
+ FileUtils.cp(path, new_path) unless new_path == path
198
+ else
199
+ File.open(new_path, "wb") { |f| f.write(read) }
200
+ end
201
+ chmod!(new_path, permissions)
202
+ self.class.new(new_path)
203
+ end
204
+
205
+ ##
206
+ # Removes the file from the filesystem.
207
+ #
208
+ def delete
209
+ FileUtils.rm(self.path) if exists?
210
+ end
211
+
212
+ ##
213
+ # Returns the content type of the file.
214
+ #
215
+ # === Returns
216
+ #
217
+ # [String] the content type of the file
218
+ #
219
+ def content_type
220
+ return @content_type if @content_type
221
+ @file.content_type.chomp if @file.respond_to?(:content_type) and @file.content_type
222
+ end
223
+
224
+ private
225
+
226
+ def file=(file)
227
+ if file.is_a?(Hash)
228
+ @file = file["tempfile"] || file[:tempfile]
229
+ @original_filename = file["filename"] || file[:filename]
230
+ @content_type = file["content_type"] || file[:content_type]
231
+ else
232
+ @file = file
233
+ @original_filename = nil
234
+ @content_type = nil
235
+ end
236
+ end
237
+
238
+ # create the directory if it doesn't exist
239
+ def mkdir!(path)
240
+ FileUtils.mkdir_p(File.dirname(path)) unless File.exists?(File.dirname(path))
241
+ end
242
+
243
+ def chmod!(path, permissions)
244
+ File.chmod(permissions, path) if permissions
245
+ end
246
+
247
+ # Sanitize the filename, to prevent hacking
248
+ def sanitize(name)
249
+ name = name.gsub("\\", "/") # work-around for IE
250
+ name = File.basename(name)
251
+ name = name.gsub(/[^a-zA-Z0-9\.\-\+_]/,"_")
252
+ name = "_#{name}" if name =~ /\A\.+\z/
253
+ name = "unnamed" if name.size == 0
254
+ return name.downcase
255
+ end
256
+
257
+ def split_extension(filename)
258
+ # regular expressions to try for identifying extensions
259
+ extension_matchers = [
260
+ /\A(.+)\.(tar\.gz)\z/, # matches "something.tar.gz"
261
+ /\A(.+)\.([^\.]+)\z/ # matches "something.jpg"
262
+ ]
263
+
264
+ extension_matchers.each do |regexp|
265
+ if filename =~ regexp
266
+ return $1, $2
267
+ end
268
+ end
269
+ return filename, "" # In case we weren't able to split the extension
270
+ end
271
+
272
+ end # SanitizedFile
273
+ end # CarrierWave