durran-carrierwave 0.3.2.3

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 (91) hide show
  1. data/Generators +4 -0
  2. data/History.txt +66 -0
  3. data/LICENSE +8 -0
  4. data/Manifest.txt +89 -0
  5. data/README.rdoc +342 -0
  6. data/Rakefile +30 -0
  7. data/carrierwave.gemspec +57 -0
  8. data/cucumber.yml +2 -0
  9. data/features/caching.feature +28 -0
  10. data/features/file_storage.feature +37 -0
  11. data/features/file_storage_overridden_filename.feature +38 -0
  12. data/features/file_storage_overridden_store_dir.feature +38 -0
  13. data/features/file_storage_reversing_processor.feature +43 -0
  14. data/features/fixtures/bork.txt +1 -0
  15. data/features/fixtures/monkey.txt +1 -0
  16. data/features/mount_activerecord.feature +46 -0
  17. data/features/mount_datamapper.feature +46 -0
  18. data/features/step_definitions/activerecord_steps.rb +22 -0
  19. data/features/step_definitions/caching_steps.rb +14 -0
  20. data/features/step_definitions/datamapper_steps.rb +29 -0
  21. data/features/step_definitions/file_steps.rb +42 -0
  22. data/features/step_definitions/general_steps.rb +80 -0
  23. data/features/step_definitions/mount_steps.rb +19 -0
  24. data/features/step_definitions/store_steps.rb +18 -0
  25. data/features/support/activerecord.rb +30 -0
  26. data/features/support/datamapper.rb +7 -0
  27. data/features/support/env.rb +35 -0
  28. data/features/versions_basics.feature +50 -0
  29. data/features/versions_nested_versions.feature +70 -0
  30. data/features/versions_overridden_filename.feature +51 -0
  31. data/features/versions_overriden_store_dir.feature +41 -0
  32. data/lib/carrierwave.rb +145 -0
  33. data/lib/carrierwave/compatibility/paperclip.rb +95 -0
  34. data/lib/carrierwave/core_ext/blank.rb +46 -0
  35. data/lib/carrierwave/core_ext/inheritable_attributes.rb +104 -0
  36. data/lib/carrierwave/core_ext/module_setup.rb +51 -0
  37. data/lib/carrierwave/mount.rb +332 -0
  38. data/lib/carrierwave/orm/activerecord.rb +73 -0
  39. data/lib/carrierwave/orm/datamapper.rb +27 -0
  40. data/lib/carrierwave/orm/mongomapper.rb +27 -0
  41. data/lib/carrierwave/orm/sequel.rb +57 -0
  42. data/lib/carrierwave/processing/image_science.rb +72 -0
  43. data/lib/carrierwave/processing/rmagick.rb +286 -0
  44. data/lib/carrierwave/sanitized_file.rb +272 -0
  45. data/lib/carrierwave/storage/abstract.rb +32 -0
  46. data/lib/carrierwave/storage/file.rb +50 -0
  47. data/lib/carrierwave/storage/s3.rb +215 -0
  48. data/lib/carrierwave/test/matchers.rb +114 -0
  49. data/lib/carrierwave/uploader.rb +43 -0
  50. data/lib/carrierwave/uploader/cache.rb +116 -0
  51. data/lib/carrierwave/uploader/callbacks.rb +42 -0
  52. data/lib/carrierwave/uploader/default_path.rb +23 -0
  53. data/lib/carrierwave/uploader/extension_whitelist.rb +37 -0
  54. data/lib/carrierwave/uploader/mountable.rb +39 -0
  55. data/lib/carrierwave/uploader/paths.rb +27 -0
  56. data/lib/carrierwave/uploader/processing.rb +81 -0
  57. data/lib/carrierwave/uploader/proxy.rb +62 -0
  58. data/lib/carrierwave/uploader/remove.rb +23 -0
  59. data/lib/carrierwave/uploader/store.rb +156 -0
  60. data/lib/carrierwave/uploader/url.rb +24 -0
  61. data/lib/carrierwave/uploader/versions.rb +147 -0
  62. data/lib/generators/uploader_generator.rb +22 -0
  63. data/rails_generators/uploader/USAGE +2 -0
  64. data/rails_generators/uploader/templates/uploader.rb +47 -0
  65. data/rails_generators/uploader/uploader_generator.rb +21 -0
  66. data/script/console +10 -0
  67. data/script/destroy +14 -0
  68. data/script/generate +14 -0
  69. data/spec/compatibility/paperclip_spec.rb +43 -0
  70. data/spec/fixtures/bork.txt +1 -0
  71. data/spec/fixtures/test.jpeg +1 -0
  72. data/spec/fixtures/test.jpg +1 -0
  73. data/spec/mount_spec.rb +517 -0
  74. data/spec/orm/activerecord_spec.rb +271 -0
  75. data/spec/orm/datamapper_spec.rb +161 -0
  76. data/spec/orm/mongomapper_spec.rb +184 -0
  77. data/spec/orm/sequel_spec.rb +192 -0
  78. data/spec/sanitized_file_spec.rb +612 -0
  79. data/spec/spec_helper.rb +99 -0
  80. data/spec/uploader/cache_spec.rb +196 -0
  81. data/spec/uploader/default_path_spec.rb +68 -0
  82. data/spec/uploader/extension_whitelist_spec.rb +44 -0
  83. data/spec/uploader/mountable_spec.rb +33 -0
  84. data/spec/uploader/paths_spec.rb +22 -0
  85. data/spec/uploader/processing_spec.rb +62 -0
  86. data/spec/uploader/proxy_spec.rb +54 -0
  87. data/spec/uploader/remove_spec.rb +70 -0
  88. data/spec/uploader/store_spec.rb +274 -0
  89. data/spec/uploader/url_spec.rb +87 -0
  90. data/spec/uploader/versions_spec.rb +306 -0
  91. metadata +228 -0
@@ -0,0 +1,272 @@
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.read(@file)
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(fn)
258
+ # regular expressions to try for identifying extensions
259
+ ext_regexps = [
260
+ /\A(.+)\.([^\.]{1,3}\.[^\.]{1,4})\z/, # matches "something.tar.gz"
261
+ /\A(.+)\.([^\.]+)\z/ # matches "something.jpg"
262
+ ]
263
+ ext_regexps.each do |regexp|
264
+ if fn =~ regexp
265
+ return $1, $2
266
+ end
267
+ end
268
+ return fn, "" # In case we weren't able to split the extension
269
+ end
270
+
271
+ end # SanitizedFile
272
+ end # CarrierWave
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ module CarrierWave
4
+ module Storage
5
+
6
+ ##
7
+ # This file serves mostly as a specification for Storage engines. There is no requirement
8
+ # that storage engines must be a subclass of this class.
9
+ #
10
+ class Abstract
11
+
12
+ attr_reader :uploader
13
+
14
+ def initialize(uploader)
15
+ @uploader = uploader
16
+ end
17
+
18
+ def self.setup!; end
19
+
20
+ def identifier
21
+ uploader.filename
22
+ end
23
+
24
+ def store!(file)
25
+ end
26
+
27
+ def retrieve!(identifier)
28
+ end
29
+
30
+ end # Abstract
31
+ end # Storage
32
+ end # CarrierWave
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ module CarrierWave
4
+ module Storage
5
+
6
+ ##
7
+ # File storage stores file to the Filesystem (surprising, no?). There's really not much
8
+ # to it, it uses the store_dir defined on the uploader as the storage location. That's
9
+ # pretty much it.
10
+ #
11
+ class File < Abstract
12
+
13
+ ##
14
+ # Move the file to the uploader's store path.
15
+ #
16
+ # === Parameters
17
+ #
18
+ # [file (CarrierWave::SanitizedFile)] the file to store
19
+ #
20
+ # === Returns
21
+ #
22
+ # [CarrierWave::SanitizedFile] a sanitized file
23
+ #
24
+ def store!(file)
25
+ path = ::File.join(uploader.store_path)
26
+ path = ::File.expand_path(path, uploader.public)
27
+ file.move_to(path, CarrierWave.config[:permissions])
28
+ file
29
+ end
30
+
31
+ ##
32
+ # Retrieve the file from its store path
33
+ #
34
+ # === Parameters
35
+ #
36
+ # [identifier (String)] the filename of the file
37
+ #
38
+ # === Returns
39
+ #
40
+ # [CarrierWave::SanitizedFile] a sanitized file
41
+ #
42
+ def retrieve!(identifier)
43
+ path = ::File.join(uploader.store_path(identifier))
44
+ path = ::File.expand_path(path, uploader.public)
45
+ CarrierWave::SanitizedFile.new(path)
46
+ end
47
+
48
+ end # File
49
+ end # Storage
50
+ end # CarrierWave
@@ -0,0 +1,215 @@
1
+ # encoding: utf-8
2
+
3
+ module CarrierWave
4
+ module Storage
5
+
6
+ ##
7
+ # Uploads things to Amazon S3 webservices. It requies the aws/s3 gem. In order for
8
+ # CarrierWave to connect to Amazon S3, you'll need to specify an access key id, secret key
9
+ # and bucket
10
+ #
11
+ # CarrierWave.config[:s3][:access_key_id] = "xxxxxx"
12
+ # CarrierWave.config[:s3][:secret_access_key] = "xxxxxx"
13
+ # CarrierWave.config[:s3][:bucket] = "my_bucket_name"
14
+ #
15
+ # You can also set the access policy for the uploaded files:
16
+ #
17
+ # CarrierWave.config[:s3][:access] = :public_read
18
+ #
19
+ # Possible values are the 'canned access control policies' provided in the aws/s3 gem,
20
+ # they are:
21
+ #
22
+ # [:private] No one else has any access rights.
23
+ # [:public_read] The anonymous principal is granted READ access.
24
+ # If this policy is used on an object, it can be read from a
25
+ # browser with no authentication.
26
+ # [:public_read_write] The anonymous principal is granted READ and WRITE access.
27
+ # [:authenticated_read] Any principal authenticated as a registered Amazon S3 user
28
+ # is granted READ access.
29
+ #
30
+ # The default is :public_read, it should work in most cases.
31
+ #
32
+ # You can change the generated url to a cnamed domain by setting the cnamed config:
33
+ #
34
+ # CarrierWave.config[:s3][:cnamed] = true
35
+ #
36
+ # No the resulting url will be
37
+ #
38
+ # http://bucket_name.domain.tld/path/to/file
39
+ #
40
+ # instead of
41
+ #
42
+ # http://s3.amazonaws.com/bucket_name.domain.tld/path/to/file
43
+ #
44
+ class S3 < Abstract
45
+
46
+ class File
47
+
48
+ def initialize(path, identifier)
49
+ @path = path
50
+ @identifier = identifier
51
+ end
52
+
53
+ ##
54
+ # Returns the current path of the file on S3
55
+ #
56
+ # === Returns
57
+ #
58
+ # [String] A path
59
+ #
60
+ def path
61
+ @path
62
+ end
63
+
64
+ ##
65
+ # Returns the filename on S3
66
+ #
67
+ # === Returns
68
+ #
69
+ # [String] path to the file
70
+ #
71
+ def identifier
72
+ @identifier
73
+ end
74
+
75
+ ##
76
+ # Reads the contents of the file from S3
77
+ #
78
+ # === Returns
79
+ #
80
+ # [String] contents of the file
81
+ #
82
+ def read
83
+ AWS::S3::S3Object.value @path, bucket
84
+ end
85
+
86
+ ##
87
+ # Remove the file from Amazon S3
88
+ #
89
+ def delete
90
+ AWS::S3::S3Object.delete @path, bucket
91
+ end
92
+
93
+ ##
94
+ # Returns the url on Amazon's S3 service
95
+ #
96
+ # === Returns
97
+ #
98
+ # [String] file's url
99
+ #
100
+ def url
101
+ if CarrierWave::config[:s3][:cnamed]
102
+ ["http://", bucket, @path].compact.join('/')
103
+ else
104
+ ["http://s3.amazonaws.com", bucket, @path].compact.join('/')
105
+ end
106
+ end
107
+
108
+ def about
109
+ s3_object.about
110
+ end
111
+
112
+ def metadata
113
+ s3_object.metadata
114
+ end
115
+
116
+ def content_type
117
+ s3_object.content_type
118
+ end
119
+
120
+ def content_type=(new_content_type)
121
+ s3_object.content_type = new_content_type
122
+ end
123
+
124
+ def content_disposition
125
+ s3_object.content_disposition
126
+ end
127
+
128
+ def content_disposition=(new_disposition)
129
+ s3_object.content_disposition = new_disposition
130
+ end
131
+
132
+ def store
133
+ s3_object.store
134
+ end
135
+
136
+ def s3_object
137
+ @s3_object ||= AWS::S3::S3Object.find(@path, bucket)
138
+ end
139
+
140
+
141
+ private
142
+
143
+ def bucket
144
+ CarrierWave::Storage::S3.bucket
145
+ end
146
+
147
+ def access
148
+ CarrierWave::Storage::S3.access
149
+ end
150
+
151
+ end
152
+
153
+ ##
154
+ # === Returns
155
+ #
156
+ # [String] the bucket set in the config options
157
+ #
158
+ def self.bucket
159
+ CarrierWave.config[:s3][:bucket]
160
+ end
161
+
162
+ ##
163
+ # === Returns
164
+ #
165
+ # [Symbol] the access priviliges the uploaded files should have
166
+ #
167
+ def self.access
168
+ CarrierWave.config[:s3][:access]
169
+ end
170
+
171
+ ##
172
+ # Connect to Amazon S3
173
+ #
174
+ def self.setup!
175
+ require 'aws/s3'
176
+ AWS::S3::Base.establish_connection!(
177
+ :access_key_id => CarrierWave.config[:s3][:access_key_id],
178
+ :secret_access_key => CarrierWave.config[:s3][:secret_access_key]
179
+ )
180
+ end
181
+
182
+ ##
183
+ # Store the file on S3
184
+ #
185
+ # === Parameters
186
+ #
187
+ # [file (CarrierWave::Storage::S3::File)] the file to store
188
+ #
189
+ # === Returns
190
+ #
191
+ # [CarrierWave::Storage::S3] the stored file
192
+ #
193
+ def store!(file)
194
+ AWS::S3::S3Object.store(::File.join(uploader.store_path), file.read, self.class.bucket, :access => self.class.access)
195
+ CarrierWave::Storage::S3::File.new(uploader.store_path, uploader.filename)
196
+ end
197
+
198
+ # Do something to retrieve the file
199
+ #
200
+ # @param [CarrierWave::Uploader] uploader an uploader object
201
+ # @param [String] identifier uniquely identifies the file
202
+ #
203
+ # [identifier (String)] uniquely identifies the file
204
+ #
205
+ # === Returns
206
+ #
207
+ # [CarrierWave::Storage::S3::File] the stored file
208
+ #
209
+ def retrieve!(identifier)
210
+ CarrierWave::Storage::S3::File.new(uploader.store_path(identifier), identifier)
211
+ end
212
+
213
+ end # S3
214
+ end # Storage
215
+ end # CarrierWave