carrierwave-rails3 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
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,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,44 @@
1
+ # encoding: utf-8
2
+
3
+ module CarrierWave
4
+
5
+ ##
6
+ # See CarrierWave::Uploader::Base
7
+ #
8
+ module Uploader
9
+
10
+ ##
11
+ # An uploader is a class that allows you to easily handle the caching and storage of
12
+ # uploaded files. Please refer to the README for configuration options.
13
+ #
14
+ # Once you have an uploader you can use it in isolation:
15
+ #
16
+ # my_uploader = MyUploader.new
17
+ # my_uploader.cache!(File.open(path_to_file))
18
+ # my_uploader.retrieve_from_store!('monkey.png')
19
+ #
20
+ # Alternatively, you can mount it on an ORM or other persistence layer, with
21
+ # +CarrierWave::Mount#mount_uploader+. There are extensions for activerecord and datamapper
22
+ # these are *very* simple (they are only a dozen lines of code), so adding your own should
23
+ # be trivial.
24
+ #
25
+ class Base
26
+ attr_reader :file
27
+
28
+ include CarrierWave::Uploader::Callbacks
29
+ include CarrierWave::Uploader::Proxy
30
+ include CarrierWave::Uploader::Url
31
+ include CarrierWave::Uploader::Mountable
32
+ include CarrierWave::Uploader::Cache
33
+ include CarrierWave::Uploader::Store
34
+ include CarrierWave::Uploader::Download
35
+ include CarrierWave::Uploader::Remove
36
+ include CarrierWave::Uploader::ExtensionWhitelist
37
+ include CarrierWave::Uploader::Processing
38
+ include CarrierWave::Uploader::Versions
39
+ include CarrierWave::Uploader::DefaultUrl
40
+ include CarrierWave::Uploader::Configuration
41
+ end # Base
42
+
43
+ end # Uploader
44
+ end # CarrierWave
@@ -0,0 +1,146 @@
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?
91
+
92
+ unless new_file.empty?
93
+ with_callbacks(:cache, new_file) do
94
+ self.cache_id = CarrierWave.generate_cache_id unless cache_id
95
+
96
+ @filename = new_file.filename
97
+ self.original_filename = new_file.filename
98
+
99
+ @file = new_file.copy_to(cache_path, permissions)
100
+ end
101
+ end
102
+ end
103
+
104
+ ##
105
+ # Retrieves the file with the given cache_name from the cache.
106
+ #
107
+ # === Parameters
108
+ #
109
+ # [cache_name (String)] uniquely identifies a cache file
110
+ #
111
+ # === Raises
112
+ #
113
+ # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
114
+ #
115
+ def retrieve_from_cache!(cache_name)
116
+ with_callbacks(:retrieve_from_cache, cache_name) do
117
+ self.cache_id, self.original_filename = cache_name.to_s.split('/', 2)
118
+ @filename = original_filename
119
+ @file = CarrierWave::SanitizedFile.new(cache_path)
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def cache_path
126
+ File.expand_path(File.join(cache_dir, cache_name), root)
127
+ end
128
+
129
+ attr_reader :cache_id, :original_filename
130
+
131
+ # We can override the full_original_filename method in other modules
132
+ alias_method :full_original_filename, :original_filename
133
+
134
+ def cache_id=(cache_id)
135
+ raise CarrierWave::InvalidParameter, "invalid cache id" unless cache_id =~ /\A[\d]{8}\-[\d]{4}\-[\d]+\-[\d]{4}\z/
136
+ @cache_id = cache_id
137
+ end
138
+
139
+ def original_filename=(filename)
140
+ raise CarrierWave::InvalidParameter, "invalid filename" unless filename =~ /\A[a-z0-9\.\-\+_]+\z/i
141
+ @original_filename = filename
142
+ end
143
+
144
+ end # Cache
145
+ end # Uploader
146
+ 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
@@ -0,0 +1,134 @@
1
+ module CarrierWave
2
+
3
+ module Uploader
4
+ module Configuration
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ add_config :root
9
+ add_config :permissions
10
+ add_config :storage_engines
11
+ add_config :s3_access # for old aws/s3
12
+ add_config :s3_access_policy # for aws
13
+ add_config :s3_bucket
14
+ add_config :s3_access_key_id
15
+ add_config :s3_secret_access_key
16
+ add_config :s3_cnamed
17
+ add_config :s3_headers
18
+ add_config :s3_multi_thread
19
+ add_config :cloud_files_username
20
+ add_config :cloud_files_api_key
21
+ add_config :cloud_files_container
22
+ add_config :grid_fs_database
23
+ add_config :grid_fs_host
24
+ add_config :grid_fs_port
25
+ add_config :grid_fs_username
26
+ add_config :grid_fs_password
27
+ add_config :grid_fs_access_url
28
+ add_config :store_dir
29
+ add_config :cache_dir
30
+ add_config :enable_processing
31
+
32
+ # Mounting
33
+ add_config :ignore_integrity_errors
34
+ add_config :ignore_processing_errors
35
+ add_config :validate_integrity
36
+ add_config :validate_processing
37
+ add_config :mount_on
38
+
39
+ configure do |config|
40
+ config.permissions = 0644
41
+ config.storage_engines = {
42
+ :file => "CarrierWave::Storage::File",
43
+ :s3 => "CarrierWave::Storage::S3",
44
+ :grid_fs => "CarrierWave::Storage::GridFS",
45
+ :right_s3 => "CarrierWave::Storage::RightS3",
46
+ :cloud_files => "CarrierWave::Storage::CloudFiles"
47
+ }
48
+ config.storage = :file
49
+ #config.s3_access = :public_read
50
+ #config.s3_access_policy = 'public-read' # Now set in library
51
+ config.s3_headers = {}
52
+ config.s3_multi_thread = true
53
+ config.grid_fs_database = 'carrierwave'
54
+ config.grid_fs_host = 'localhost'
55
+ config.grid_fs_port = 27017
56
+ config.store_dir = 'uploads'
57
+ config.cache_dir = 'uploads/tmp'
58
+ config.ignore_integrity_errors = true
59
+ config.ignore_processing_errors = true
60
+ config.validate_integrity = true
61
+ config.validate_processing = true
62
+ config.root = CarrierWave.root
63
+ config.enable_processing = true
64
+ end
65
+ end
66
+
67
+ module ClassMethods
68
+
69
+ ##
70
+ # Sets the storage engine to be used when storing files with this uploader.
71
+ # Can be any class that implements a #store!(CarrierWave::SanitizedFile) and a #retrieve!
72
+ # method. See lib/carrierwave/storage/file.rb for an example. Storage engines should
73
+ # be added to CarrierWave::Uploader::Base.storage_engines so they can be referred
74
+ # to by a symbol, which should be more convenient
75
+ #
76
+ # If no argument is given, it will simply return the currently used storage engine.
77
+ #
78
+ # === Parameters
79
+ #
80
+ # [storage (Symbol, Class)] The storage engine to use for this uploader
81
+ #
82
+ # === Returns
83
+ #
84
+ # [Class] the storage engine to be used with this uploader
85
+ #
86
+ # === Examples
87
+ #
88
+ # storage :file
89
+ # storage CarrierWave::Storage::File
90
+ # storage MyCustomStorageEngine
91
+ #
92
+ def storage(storage = nil)
93
+ if storage.is_a?(Symbol)
94
+ @storage = eval(storage_engines[storage])
95
+ elsif storage
96
+ @storage = storage
97
+ elsif @storage.nil?
98
+ # Get the storage from the superclass if there is one
99
+ @storage = superclass.storage rescue nil
100
+ end
101
+ return @storage
102
+ end
103
+ alias_method :storage=, :storage
104
+
105
+
106
+ def add_config(name)
107
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
108
+ def self.#{name}(value=nil)
109
+ @#{name} = value if value
110
+ return @#{name} if self.object_id == #{self.object_id} || defined?(@#{name})
111
+ name = superclass.#{name}
112
+ return nil if name.nil? && !instance_variable_defined?("@#{name}")
113
+ @#{name} = name && !name.is_a?(Module) && !name.is_a?(Symbol) && !name.is_a?(Numeric) && !name.is_a?(TrueClass) && !name.is_a?(FalseClass) ? name.dup : name
114
+ end
115
+
116
+ def self.#{name}=(value)
117
+ @#{name} = value
118
+ end
119
+
120
+ def #{name}
121
+ self.class.#{name}
122
+ end
123
+ RUBY
124
+ end
125
+
126
+ def configure
127
+ yield self
128
+ end
129
+ end
130
+
131
+ end
132
+ end
133
+ end
134
+