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,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
+