locomotive_carrierwave 0.5.0.1.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/README.rdoc +532 -0
  2. data/lib/carrierwave/compatibility/paperclip.rb +95 -0
  3. data/lib/carrierwave/locale/en.yml +5 -0
  4. data/lib/carrierwave/mount.rb +376 -0
  5. data/lib/carrierwave/orm/activerecord.rb +36 -0
  6. data/lib/carrierwave/orm/datamapper.rb +37 -0
  7. data/lib/carrierwave/orm/mongoid.rb +36 -0
  8. data/lib/carrierwave/orm/sequel.rb +45 -0
  9. data/lib/carrierwave/processing/image_science.rb +116 -0
  10. data/lib/carrierwave/processing/mini_magick.rb +261 -0
  11. data/lib/carrierwave/processing/rmagick.rb +278 -0
  12. data/lib/carrierwave/sanitized_file.rb +306 -0
  13. data/lib/carrierwave/storage/abstract.rb +33 -0
  14. data/lib/carrierwave/storage/cloud_files.rb +168 -0
  15. data/lib/carrierwave/storage/file.rb +54 -0
  16. data/lib/carrierwave/storage/grid_fs.rb +136 -0
  17. data/lib/carrierwave/storage/right_s3.rb +1 -0
  18. data/lib/carrierwave/storage/s3.rb +249 -0
  19. data/lib/carrierwave/test/matchers.rb +164 -0
  20. data/lib/carrierwave/uploader/cache.rb +148 -0
  21. data/lib/carrierwave/uploader/callbacks.rb +41 -0
  22. data/lib/carrierwave/uploader/configuration.rb +134 -0
  23. data/lib/carrierwave/uploader/default_url.rb +19 -0
  24. data/lib/carrierwave/uploader/download.rb +64 -0
  25. data/lib/carrierwave/uploader/extension_whitelist.rb +38 -0
  26. data/lib/carrierwave/uploader/mountable.rb +39 -0
  27. data/lib/carrierwave/uploader/processing.rb +85 -0
  28. data/lib/carrierwave/uploader/proxy.rb +62 -0
  29. data/lib/carrierwave/uploader/remove.rb +23 -0
  30. data/lib/carrierwave/uploader/rename.rb +62 -0
  31. data/lib/carrierwave/uploader/store.rb +98 -0
  32. data/lib/carrierwave/uploader/url.rb +33 -0
  33. data/lib/carrierwave/uploader/versions.rb +157 -0
  34. data/lib/carrierwave/uploader.rb +45 -0
  35. data/lib/carrierwave/validations/active_model.rb +79 -0
  36. data/lib/carrierwave/version.rb +3 -0
  37. data/lib/carrierwave.rb +101 -0
  38. data/lib/generators/templates/uploader.rb +47 -0
  39. data/lib/generators/uploader_generator.rb +7 -0
  40. metadata +390 -0
@@ -0,0 +1,249 @@
1
+ # encoding: utf-8
2
+ begin
3
+ require 'fog'
4
+ rescue LoadError
5
+ raise "You don't have the 'fog' gem installed. The 'aws', 'aws-s3' and 'right_aws' gems are no longer supported."
6
+ end
7
+
8
+ module CarrierWave
9
+ module Storage
10
+
11
+ ##
12
+ # Uploads things to Amazon S3 using the "fog" gem.
13
+ # You'll need to specify the access_key_id, secret_access_key and bucket.
14
+ #
15
+ # CarrierWave.configure do |config|
16
+ # config.s3_access_key_id = "xxxxxx"
17
+ # config.s3_secret_access_key = "xxxxxx"
18
+ # config.s3_bucket = "my_bucket_name"
19
+ # end
20
+ #
21
+ # You can set the access policy for the uploaded files:
22
+ #
23
+ # CarrierWave.configure do |config|
24
+ # config.s3_access_policy = :public_read
25
+ # end
26
+ #
27
+ # The default is :public_read. For more options see:
28
+ #
29
+ # http://docs.amazonwebservices.com/AmazonS3/latest/RESTAccessPolicy.html#RESTCannedAccessPolicies
30
+ #
31
+ # The following access policies are available:
32
+ #
33
+ # [:private] No one else has any access rights.
34
+ # [:public_read] The anonymous principal is granted READ access.
35
+ # If this policy is used on an object, it can be read from a
36
+ # browser with no authentication.
37
+ # [:public_read_write] The anonymous principal is granted READ and WRITE access.
38
+ # [:authenticated_read] Any principal authenticated as a registered Amazon S3 user
39
+ # is granted READ access.
40
+ #
41
+ # You can change the generated url to a cnamed domain by setting the cname config:
42
+ #
43
+ # CarrierWave.configure do |config|
44
+ # config.s3_cname = 'bucketname.domain.tld'
45
+ # config.s3_bucket = 'bucketname' # only used when storing / deleting files
46
+ # end
47
+ #
48
+ # Now the resulting url will be
49
+ #
50
+ # http://bucketname.domain.tld/path/to/file
51
+ #
52
+ # instead of
53
+ #
54
+ # http://bucketname.domain.tld.s3.amazonaws.com/path/to/file
55
+ #
56
+ class S3 < Abstract
57
+
58
+ class File
59
+
60
+ def initialize(uploader, base, path)
61
+ @uploader = uploader
62
+ @path = path
63
+ @base = base
64
+ end
65
+
66
+ ##
67
+ # Returns the current path of the file on S3
68
+ #
69
+ # === Returns
70
+ #
71
+ # [String] A path
72
+ #
73
+ def path
74
+ @path
75
+ end
76
+
77
+ ##
78
+ # Reads the contents of the file from S3
79
+ #
80
+ # === Returns
81
+ #
82
+ # [String] contents of the file
83
+ #
84
+ def read
85
+ result = connection.get_object(bucket, @path)
86
+ @headers = result.headers
87
+ result.body
88
+ end
89
+
90
+ ##
91
+ # Remove the file from Amazon S3
92
+ #
93
+ def delete
94
+ connection.delete_object(bucket, @path)
95
+ end
96
+
97
+ def rename(new_path)
98
+ file = connection.put_object(bucket, new_path, read,
99
+ {
100
+ 'x-amz-acl' => access_policy.to_s.gsub('_', '-'),
101
+ 'Content-Type' => content_type
102
+ }.merge(@uploader.s3_headers)
103
+ )
104
+ delete
105
+ file
106
+ end
107
+
108
+ ##
109
+ # Returns the url on Amazon's S3 service
110
+ #
111
+ # === Returns
112
+ #
113
+ # [String] file's url
114
+ #
115
+ def url
116
+ if access_policy == :authenticated_read
117
+ authenticated_url
118
+ else
119
+ public_url
120
+ end
121
+ end
122
+
123
+ def public_url
124
+ if cnamed?
125
+ ["http://#{cname}", path].compact.join('/')
126
+ else
127
+ ["http://#{bucket}.s3.amazonaws.com", path].compact.join('/')
128
+ end
129
+ end
130
+
131
+ def authenticated_url
132
+ connection.get_object_url(bucket, path, Time.now + 60 * 10)
133
+ end
134
+
135
+ def store(file)
136
+ content_type ||= file.content_type # this might cause problems if content type changes between read and upload (unlikely)
137
+ connection.put_object(bucket, path, file.read,
138
+ {
139
+ 'x-amz-acl' => access_policy.to_s.gsub('_', '-'),
140
+ 'Content-Type' => content_type,
141
+ }.merge(@uploader.s3_headers)
142
+ )
143
+ end
144
+
145
+ def content_type
146
+ headers["Content-Type"]
147
+ end
148
+
149
+ def content_type=(type)
150
+ headers["Content-Type"] = type
151
+ end
152
+
153
+ def size
154
+ headers['Content-Length'].to_i
155
+ end
156
+
157
+ # Headers returned from file retrieval
158
+ def headers
159
+ @headers ||= begin
160
+ connection.head_object(bucket, @path).headers
161
+ rescue Excon::Errors::NotFound # Don't die, just return no headers
162
+ {}
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ def cname
169
+ @uploader.s3_cname
170
+ end
171
+
172
+ def cnamed?
173
+ !@uploader.s3_cname.nil?
174
+ end
175
+
176
+ def access_policy
177
+ @uploader.s3_access_policy
178
+ end
179
+
180
+ def bucket
181
+ @uploader.s3_bucket
182
+ end
183
+
184
+ def connection
185
+ @base.connection
186
+ end
187
+
188
+ end
189
+
190
+ ##
191
+ # Store the file on S3
192
+ #
193
+ # === Parameters
194
+ #
195
+ # [file (CarrierWave::SanitizedFile)] the file to store
196
+ #
197
+ # === Returns
198
+ #
199
+ # [CarrierWave::Storage::S3::File] the stored file
200
+ #
201
+ def store!(file)
202
+ f = CarrierWave::Storage::S3::File.new(uploader, self, uploader.store_path)
203
+ f.store(file)
204
+ f
205
+ end
206
+
207
+ # Do something to retrieve the file
208
+ #
209
+ # @param [String] identifier uniquely identifies the file
210
+ #
211
+ # [identifier (String)] uniquely identifies the file
212
+ #
213
+ # === Returns
214
+ #
215
+ # [CarrierWave::Storage::S3::File] the stored file
216
+ #
217
+ def retrieve!(identifier)
218
+ CarrierWave::Storage::S3::File.new(uploader, self, uploader.store_path(identifier))
219
+ end
220
+
221
+ ##
222
+ # Rename a file on S3
223
+ #
224
+ # === Parameters
225
+ #
226
+ # [file (CarrierWave::Storage::S3::File)] the file to rename
227
+ #
228
+ # === Returns
229
+ #
230
+ # [CarrierWave::Storage::S3::File] the renamed file
231
+ #
232
+ def rename!(file)
233
+ path = uploader.store_path
234
+ file.rename(path)
235
+ CarrierWave::Storage::S3::File.new(uploader, self, path)
236
+ end
237
+
238
+ def connection
239
+ @connection ||= Fog::AWS::Storage.new(
240
+ :aws_access_key_id => uploader.s3_access_key_id,
241
+ :aws_secret_access_key => uploader.s3_secret_access_key
242
+ )
243
+ end
244
+
245
+ end # S3
246
+ end # Storage
247
+ end # CarrierWave
248
+
249
+ # module CarrierWave; module Storage; class S3 < Abstract; def connection; @connection ||= Fog::AWS::Storage.new(:aws_access_key_id => uploader.s3_access_key_id, :aws_secret_access_key => uploader.s3_secret_access_key, :host => uploader.s3_cnamed ? uploader.s3_bucket : nil); end; end; end; end
@@ -0,0 +1,164 @@
1
+ # encoding: utf-8
2
+
3
+ module CarrierWave
4
+ module Test
5
+
6
+ ##
7
+ # These are some matchers that can be used in RSpec specs, to simplify the testing
8
+ # of uploaders.
9
+ #
10
+ module Matchers
11
+
12
+ class BeIdenticalTo # :nodoc:
13
+ def initialize(expected)
14
+ @expected = expected
15
+ end
16
+ def matches?(actual)
17
+ @actual = actual
18
+ FileUtils.identical?(@actual, @expected)
19
+ end
20
+ def failure_message
21
+ "expected #{@actual.inspect} to be identical to #{@expected.inspect}"
22
+ end
23
+ def negative_failure_message
24
+ "expected #{@actual.inspect} to not be identical to #{@expected.inspect}"
25
+ end
26
+ end
27
+
28
+ def be_identical_to(expected)
29
+ BeIdenticalTo.new(expected)
30
+ end
31
+
32
+ class HavePermissions # :nodoc:
33
+ def initialize(expected)
34
+ @expected = expected
35
+ end
36
+
37
+ def matches?(actual)
38
+ @actual = actual
39
+ # Satisfy expectation here. Return false or raise an error if it's not met.
40
+ (File.stat(@actual.path).mode & 0777) == @expected
41
+ end
42
+
43
+ def failure_message
44
+ "expected #{@actual.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0777).to_s(8)}"
45
+ end
46
+
47
+ def negative_failure_message
48
+ "expected #{@actual.inspect} not to have permissions #{@expected.to_s(8)}, but it did"
49
+ end
50
+ end
51
+
52
+ def have_permissions(expected)
53
+ HavePermissions.new(expected)
54
+ end
55
+
56
+ class BeNoLargerThan # :nodoc:
57
+ def initialize(width, height)
58
+ @width, @height = width, height
59
+ end
60
+
61
+ def matches?(actual)
62
+ @actual = actual
63
+ # Satisfy expectation here. Return false or raise an error if it's not met.
64
+ image = ImageLoader.load_image(@actual.current_path)
65
+ @actual_width = image.width
66
+ @actual_height = image.height
67
+ @actual_width <= @width && @actual_height <= @height
68
+ end
69
+
70
+ def failure_message
71
+ "expected #{@actual.current_path.inspect} to be no larger than #{@width} by #{@height}, but it was #{@actual_width} by #{@actual_height}."
72
+ end
73
+
74
+ def negative_failure_message
75
+ "expected #{@actual.current_path.inspect} to be larger than #{@width} by #{@height}, but it wasn't."
76
+ end
77
+
78
+ end
79
+
80
+ def be_no_larger_than(width, height)
81
+ BeNoLargerThan.new(width, height)
82
+ end
83
+
84
+ class HaveDimensions # :nodoc:
85
+ def initialize(width, height)
86
+ @width, @height = width, height
87
+ end
88
+
89
+ def matches?(actual)
90
+ @actual = actual
91
+ # Satisfy expectation here. Return false or raise an error if it's not met.
92
+ image = ImageLoader.load_image(@actual.current_path)
93
+ @actual_width = image.width
94
+ @actual_height = image.height
95
+ @actual_width == @width && @actual_height == @height
96
+ end
97
+
98
+ def failure_message
99
+ "expected #{@actual.current_path.inspect} to have an exact size of #{@width} by #{@height}, but it was #{@actual_width} by #{@actual_height}."
100
+ end
101
+
102
+ def negative_failure_message
103
+ "expected #{@actual.current_path.inspect} not to have an exact size of #{@width} by #{@height}, but it did."
104
+ end
105
+
106
+ end
107
+
108
+ def have_dimensions(width, height)
109
+ HaveDimensions.new(width, height)
110
+ end
111
+
112
+ class ImageLoader # :nodoc:
113
+ def self.load_image(filename)
114
+ if defined? ::MiniMagick
115
+ MiniMagickWrapper.new(filename)
116
+ else
117
+ unless defined? ::Magick
118
+ begin
119
+ require 'rmagick'
120
+ rescue LoadError
121
+ require 'RMagick'
122
+ rescue LoadError
123
+ puts "WARNING: Failed to require rmagick, image processing may fail!"
124
+ end
125
+ end
126
+ MagickWrapper.new(filename)
127
+ end
128
+ end
129
+ end
130
+
131
+ class MagickWrapper # :nodoc:
132
+ attr_reader :image
133
+ def width
134
+ image.columns
135
+ end
136
+
137
+ def height
138
+ image.rows
139
+ end
140
+
141
+ def initialize(filename)
142
+ @image = ::Magick::Image.read(filename).first
143
+ end
144
+ end
145
+
146
+ class MiniMagickWrapper # :nodoc:
147
+ attr_reader :image
148
+ def width
149
+ image[:width]
150
+ end
151
+
152
+ def height
153
+ image[:height]
154
+ end
155
+
156
+ def initialize(filename)
157
+ @image = ::MiniMagick::Image.from_file(filename)
158
+ end
159
+ end
160
+
161
+ end # Matchers
162
+ end # Test
163
+ end # CarrierWave
164
+
@@ -0,0 +1,148 @@
1
+ # encoding: utf-8
2
+
3
+ module CarrierWave
4
+
5
+ class FormNotMultipart < UploadError
6
+ def message
7
+ "You tried to assign a String or a Pathname to an uploader, for security reasons, this is not allowed.\n\n If this is a file upload, please check that your upload form is multipart encoded."
8
+ end
9
+ end
10
+
11
+ ##
12
+ # Generates a unique cache id for use in the caching system
13
+ #
14
+ # === Returns
15
+ #
16
+ # [String] a cache id in the format YYYYMMDD-HHMM-PID-RND
17
+ #
18
+ def self.generate_cache_id
19
+ Time.now.strftime('%Y%m%d-%H%M') + '-' + Process.pid.to_s + '-' + ("%04d" % rand(9999))
20
+ end
21
+
22
+ module Uploader
23
+ module Cache
24
+ extend ActiveSupport::Concern
25
+
26
+ include CarrierWave::Uploader::Callbacks
27
+ include CarrierWave::Uploader::Configuration
28
+
29
+ module ClassMethods
30
+
31
+ ##
32
+ # Removes cached files which are older than one day. You could call this method
33
+ # from a rake task to clean out old cached files.
34
+ #
35
+ # You can call this method directly on the module like this:
36
+ #
37
+ # CarrierWave.clean_cached_files!
38
+ #
39
+ # === Note
40
+ #
41
+ # This only works as long as you haven't done anything funky with your cache_dir.
42
+ # It's recommended that you keep cache files in one place only.
43
+ #
44
+ def clean_cached_files!
45
+ Dir.glob(File.expand_path(File.join(cache_dir, '*'), CarrierWave.root)).each do |dir|
46
+ time = dir.scan(/(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})/).first.map { |t| t.to_i }
47
+ time = Time.utc(*time)
48
+ if time < (Time.now - (60*60*24))
49
+ FileUtils.rm_rf(dir)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ ##
56
+ # Returns true if the uploader has been cached
57
+ #
58
+ # === Returns
59
+ #
60
+ # [Bool] whether the current file is cached
61
+ #
62
+ def cached?
63
+ @cache_id
64
+ end
65
+
66
+ ##
67
+ # Returns a String which uniquely identifies the currently cached file for later retrieval
68
+ #
69
+ # === Returns
70
+ #
71
+ # [String] a cache name, in the format YYYYMMDD-HHMM-PID-RND/filename.txt
72
+ #
73
+ def cache_name
74
+ File.join(cache_id, full_original_filename) if cache_id and original_filename
75
+ end
76
+
77
+ ##
78
+ # Caches the given file. Calls process! to trigger any process callbacks.
79
+ #
80
+ # === Parameters
81
+ #
82
+ # [new_file (File, IOString, Tempfile)] any kind of file object
83
+ #
84
+ # === Raises
85
+ #
86
+ # [CarrierWave::FormNotMultipart] if the assigned parameter is a string
87
+ #
88
+ def cache!(new_file)
89
+ new_file = CarrierWave::SanitizedFile.new(new_file)
90
+ raise CarrierWave::FormNotMultipart if new_file.is_path? && ensure_multipart_form
91
+
92
+ unless new_file.empty?
93
+ with_callbacks(:cache, new_file) do
94
+ @original_file ||= self.file.clone if self.file
95
+
96
+ self.cache_id = CarrierWave.generate_cache_id unless cache_id
97
+
98
+ @filename = new_file.filename
99
+ self.original_filename = new_file.filename
100
+
101
+ @file = new_file.copy_to(cache_path, permissions)
102
+ end
103
+ end
104
+ end
105
+
106
+ ##
107
+ # Retrieves the file with the given cache_name from the cache.
108
+ #
109
+ # === Parameters
110
+ #
111
+ # [cache_name (String)] uniquely identifies a cache file
112
+ #
113
+ # === Raises
114
+ #
115
+ # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
116
+ #
117
+ def retrieve_from_cache!(cache_name)
118
+ with_callbacks(:retrieve_from_cache, cache_name) do
119
+ self.cache_id, self.original_filename = cache_name.to_s.split('/', 2)
120
+ @filename = original_filename
121
+ @file = CarrierWave::SanitizedFile.new(cache_path)
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def cache_path
128
+ File.expand_path(File.join(cache_dir, cache_name), root)
129
+ end
130
+
131
+ attr_reader :cache_id, :original_filename
132
+
133
+ # We can override the full_original_filename method in other modules
134
+ alias_method :full_original_filename, :original_filename
135
+
136
+ def cache_id=(cache_id)
137
+ raise CarrierWave::InvalidParameter, "invalid cache id" unless cache_id =~ /\A[\d]{8}\-[\d]{4}\-[\d]+\-[\d]{4}\z/
138
+ @cache_id = cache_id
139
+ end
140
+
141
+ def original_filename=(filename)
142
+ raise CarrierWave::InvalidParameter, "invalid filename" unless filename =~ /\A[a-z0-9\.\-\+_]+\z/i
143
+ @original_filename = filename
144
+ end
145
+
146
+ end # Cache
147
+ end # Uploader
148
+ end # CarrierWave
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ module CarrierWave
4
+ module Uploader
5
+ module Callbacks
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_inheritable_accessor :_before_callbacks, :_after_callbacks
10
+ end
11
+
12
+ def with_callbacks(kind, *args)
13
+ self.class._before_callbacks_for(kind).each { |callback| self.send(callback, *args) }
14
+ yield
15
+ self.class._after_callbacks_for(kind).each { |callback| self.send(callback, *args) }
16
+ end
17
+
18
+ module ClassMethods
19
+
20
+ def _before_callbacks_for(kind) #:nodoc:
21
+ (self._before_callbacks || { kind => [] })[kind] || []
22
+ end
23
+
24
+ def _after_callbacks_for(kind) #:nodoc:
25
+ (self._after_callbacks || { kind => [] })[kind] || []
26
+ end
27
+
28
+ def before(kind, callback)
29
+ self._before_callbacks ||= {}
30
+ self._before_callbacks[kind] = _before_callbacks_for(kind) + [callback]
31
+ end
32
+
33
+ def after(kind, callback)
34
+ self._after_callbacks ||= {}
35
+ self._after_callbacks[kind] = _after_callbacks_for(kind) + [callback]
36
+ end
37
+ end # ClassMethods
38
+
39
+ end # Callbacks
40
+ end # Uploader
41
+ end # CarrierWave