carrierwave 0.9.0 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of carrierwave might be problematic. Click here for more details.

Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +508 -158
  3. data/lib/carrierwave/compatibility/paperclip.rb +31 -21
  4. data/lib/carrierwave/downloader/base.rb +101 -0
  5. data/lib/carrierwave/downloader/remote_file.rb +68 -0
  6. data/lib/carrierwave/error.rb +1 -0
  7. data/lib/carrierwave/locale/en.yml +11 -5
  8. data/lib/carrierwave/mount.rb +220 -187
  9. data/lib/carrierwave/mounter.rb +255 -0
  10. data/lib/carrierwave/orm/activerecord.rb +24 -34
  11. data/lib/carrierwave/processing/mini_magick.rb +142 -79
  12. data/lib/carrierwave/processing/rmagick.rb +76 -35
  13. data/lib/carrierwave/processing/vips.rb +284 -0
  14. data/lib/carrierwave/processing.rb +1 -1
  15. data/lib/carrierwave/sanitized_file.rb +89 -70
  16. data/lib/carrierwave/storage/abstract.rb +16 -3
  17. data/lib/carrierwave/storage/file.rb +71 -3
  18. data/lib/carrierwave/storage/fog.rb +215 -58
  19. data/lib/carrierwave/storage.rb +1 -7
  20. data/lib/carrierwave/test/matchers.rb +88 -19
  21. data/lib/carrierwave/uploader/cache.rb +88 -44
  22. data/lib/carrierwave/uploader/callbacks.rb +1 -3
  23. data/lib/carrierwave/uploader/configuration.rb +81 -9
  24. data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
  25. data/lib/carrierwave/uploader/content_type_denylist.rb +62 -0
  26. data/lib/carrierwave/uploader/default_url.rb +3 -5
  27. data/lib/carrierwave/uploader/dimension.rb +66 -0
  28. data/lib/carrierwave/uploader/download.rb +5 -69
  29. data/lib/carrierwave/uploader/extension_allowlist.rb +63 -0
  30. data/lib/carrierwave/uploader/extension_denylist.rb +64 -0
  31. data/lib/carrierwave/uploader/file_size.rb +43 -0
  32. data/lib/carrierwave/uploader/mountable.rb +13 -8
  33. data/lib/carrierwave/uploader/processing.rb +54 -21
  34. data/lib/carrierwave/uploader/proxy.rb +30 -8
  35. data/lib/carrierwave/uploader/remove.rb +0 -2
  36. data/lib/carrierwave/uploader/serialization.rb +3 -5
  37. data/lib/carrierwave/uploader/store.rb +59 -28
  38. data/lib/carrierwave/uploader/url.rb +8 -7
  39. data/lib/carrierwave/uploader/versions.rb +173 -124
  40. data/lib/carrierwave/uploader.rb +12 -6
  41. data/lib/carrierwave/utilities/file_name.rb +47 -0
  42. data/lib/carrierwave/utilities/uri.rb +14 -12
  43. data/lib/carrierwave/utilities.rb +2 -3
  44. data/lib/carrierwave/validations/active_model.rb +7 -13
  45. data/lib/carrierwave/version.rb +1 -1
  46. data/lib/carrierwave.rb +41 -16
  47. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -9
  48. data/lib/generators/uploader_generator.rb +3 -3
  49. metadata +224 -100
  50. data/lib/carrierwave/locale/cs.yml +0 -11
  51. data/lib/carrierwave/locale/de.yml +0 -11
  52. data/lib/carrierwave/locale/nl.yml +0 -11
  53. data/lib/carrierwave/locale/sk.yml +0 -11
  54. data/lib/carrierwave/processing/mime_types.rb +0 -73
  55. data/lib/carrierwave/uploader/extension_blacklist.rb +0 -47
  56. data/lib/carrierwave/uploader/extension_whitelist.rb +0 -49
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Uploader
5
3
  module Mountable
@@ -7,13 +5,14 @@ module CarrierWave
7
5
  attr_reader :model, :mounted_as
8
6
 
9
7
  ##
10
- # If a model is given as the first parameter, it will be stored in the uploader, and
11
- # available throught +#model+. Likewise, mounted_as stores the name of the column
12
- # where this instance of the uploader is mounted. These values can then be used inside
13
- # your uploader.
8
+ # If a model is given as the first parameter, it will be stored in the
9
+ # uploader, and available through +#model+. Likewise, mounted_as stores
10
+ # the name of the column where this instance of the uploader is mounted.
11
+ # These values can then be used inside your uploader.
14
12
  #
15
- # If you do not wish to mount your uploaders with the ORM extensions in -more then you
16
- # can override this method inside your uploader. Just be sure to call +super+
13
+ # If you do not wish to mount your uploaders with the ORM extensions in
14
+ # -more then you can override this method inside your uploader. Just be
15
+ # sure to call +super+
17
16
  #
18
17
  # === Parameters
19
18
  #
@@ -34,6 +33,12 @@ module CarrierWave
34
33
  @mounted_as = mounted_as
35
34
  end
36
35
 
36
+ ##
37
+ # Returns array index of given uploader within currently mounted uploaders
38
+ #
39
+ def index
40
+ model.__send__(:_mounter, mounted_as).uploaders.index(self)
41
+ end
37
42
  end # Mountable
38
43
  end # Uploader
39
44
  end # CarrierWave
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Uploader
5
3
  module Processing
@@ -11,7 +9,7 @@ module CarrierWave
11
9
  class_attribute :processors, :instance_writer => false
12
10
  self.processors = []
13
11
 
14
- after :cache, :process!
12
+ before :cache, :process!
15
13
  end
16
14
 
17
15
  module ClassMethods
@@ -20,7 +18,7 @@ module CarrierWave
20
18
  # Adds a processor callback which applies operations as a file is uploaded.
21
19
  # The argument may be the name of any method of the uploader, expressed as a symbol,
22
20
  # or a list of such methods, or a hash where the key is a method and the value is
23
- # an array of arguments to call the method with
21
+ # an array of arguments to call the method with. Also accepts an :if or :unless condition
24
22
  #
25
23
  # === Parameters
26
24
  #
@@ -33,6 +31,7 @@ module CarrierWave
33
31
  # process :sepiatone, :vignette
34
32
  # process :scale => [200, 200]
35
33
  # process :scale => [200, 200], :if => :image?
34
+ # process :scale => [200, 200], :unless => :disallowed_image_type?
36
35
  # process :sepiatone, :if => :image?
37
36
  #
38
37
  # def sepiatone
@@ -51,42 +50,76 @@ module CarrierWave
51
50
  # ...
52
51
  # end
53
52
  #
53
+ # def disallowed_image_type?
54
+ # ...
55
+ # end
56
+ #
54
57
  # end
55
58
  #
56
59
  def process(*args)
57
- if !args.first.is_a?(Hash) && args.last.is_a?(Hash)
58
- conditions = args.pop
59
- args.map!{ |arg| {arg => []}.merge(conditions) }
60
+ new_processors = args.inject({}) do |hash, arg|
61
+ arg = { arg => [] } unless arg.is_a?(Hash)
62
+ hash.merge!(arg)
60
63
  end
61
64
 
62
- args.each do |arg|
63
- if arg.is_a?(Hash)
64
- condition = arg.delete(:if)
65
- arg.each do |method, args|
66
- self.processors += [[method, args, condition]]
67
- end
68
- else
69
- self.processors += [[arg, [], nil]]
65
+ condition_type = new_processors.keys.detect { |key| [:if, :unless].include?(key) }
66
+ condition = new_processors.delete(:if) || new_processors.delete(:unless)
67
+ new_processors.each do |processor, processor_args|
68
+ self.processors += [[processor, processor_args, condition, condition_type]]
69
+
70
+ if processor == :convert
71
+ # Treat :convert specially, since it should trigger the file extension change
72
+ force_extension processor_args
70
73
  end
71
74
  end
72
75
  end
73
-
74
76
  end # ClassMethods
75
77
 
76
78
  ##
77
79
  # Apply all process callbacks added through CarrierWave.process
78
80
  #
79
81
  def process!(new_file=nil)
80
- if enable_processing
81
- self.class.processors.each do |method, args, condition|
82
- if(condition)
83
- next if !(condition.respond_to?(:call) ? condition.call(self, :args => args, :method => method, :file => new_file) : self.send(condition, new_file))
82
+ return unless enable_processing
83
+
84
+ with_callbacks(:process, new_file) do
85
+ self.class.processors.each do |method, args, condition, condition_type|
86
+ if condition && condition_type == :if
87
+ if condition.respond_to?(:call)
88
+ next unless condition.call(self, :args => args, :method => method, :file => new_file)
89
+ else
90
+ next unless self.send(condition, new_file)
91
+ end
92
+ elsif condition && condition_type == :unless
93
+ if condition.respond_to?(:call)
94
+ next if condition.call(self, :args => args, :method => method, :file => new_file)
95
+ elsif self.send(condition, new_file)
96
+ next
97
+ end
98
+ end
99
+
100
+ if args.is_a? Array
101
+ kwargs, args = args.partition { |arg| arg.is_a? Hash }
102
+ end
103
+
104
+ if kwargs.present?
105
+ kwargs = kwargs.reduce(:merge)
106
+ self.send(method, *args, **kwargs)
107
+ else
108
+ self.send(method, *args)
84
109
  end
85
- self.send(method, *args)
86
110
  end
87
111
  end
88
112
  end
89
113
 
114
+ private
115
+
116
+ def forcing_extension(filename)
117
+ if force_extension && filename
118
+ Pathname.new(filename).sub_ext(".#{force_extension.to_s.delete_prefix('.')}").to_s
119
+ else
120
+ filename
121
+ end
122
+ end
90
123
  end # Processing
91
124
  end # Uploader
92
125
  end # CarrierWave
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Uploader
5
3
  module Proxy
@@ -19,20 +17,33 @@ module CarrierWave
19
17
  # [String] the path where the file is currently located.
20
18
  #
21
19
  def current_path
22
- file.path if file.respond_to?(:path)
20
+ file.try(:path)
23
21
  end
24
22
 
25
23
  alias_method :path, :current_path
26
24
 
27
25
  ##
28
- # Returns a string that uniquely identifies the last stored file
26
+ # Returns a string that uniquely identifies the retrieved or last stored file
29
27
  #
30
28
  # === Returns
31
29
  #
32
30
  # [String] uniquely identifies a file
33
31
  #
34
32
  def identifier
35
- storage.identifier if storage.respond_to?(:identifier)
33
+ @identifier || (file && storage.try(:identifier))
34
+ end
35
+
36
+ ##
37
+ # Returns a String which is to be used as a temporary value which gets assigned to the column.
38
+ # The purpose is to mark the column that it will be updated. Finally before the save it will be
39
+ # overwritten by the #identifier value, which is usually #filename.
40
+ #
41
+ # === Returns
42
+ #
43
+ # [String] a temporary_identifier, by default @original_filename is used
44
+ #
45
+ def temporary_identifier
46
+ @original_filename || @identifier
36
47
  end
37
48
 
38
49
  ##
@@ -42,8 +53,8 @@ module CarrierWave
42
53
  #
43
54
  # [String] contents of the file
44
55
  #
45
- def read
46
- file.read if file.respond_to?(:read)
56
+ def read(*args)
57
+ file.try(:read, *args)
47
58
  end
48
59
 
49
60
  ##
@@ -54,7 +65,7 @@ module CarrierWave
54
65
  # [Integer] size of the file
55
66
  #
56
67
  def size
57
- file.respond_to?(:size) ? file.size : 0
68
+ file.try(:size) || 0
58
69
  end
59
70
 
60
71
  ##
@@ -72,6 +83,17 @@ module CarrierWave
72
83
  size
73
84
  end
74
85
 
86
+ ##
87
+ # Read the content type of the file
88
+ #
89
+ # === Returns
90
+ #
91
+ # [String] content type of the file
92
+ #
93
+ def content_type
94
+ file.try(:content_type)
95
+ end
96
+
75
97
  end # Proxy
76
98
  end # Uploader
77
99
  end # CarrierWave
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Uploader
5
3
  module Remove
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  require "json"
4
2
  require "active_support/core_ext/hash"
5
3
 
@@ -8,12 +6,12 @@ module CarrierWave
8
6
  module Serialization
9
7
  extend ActiveSupport::Concern
10
8
 
11
- def serializable_hash
12
- {"url" => url}.merge Hash[versions.map { |name, version| [name, { "url" => version.url }] }]
9
+ def serializable_hash(options = nil)
10
+ {"url" => url}.merge Hash[versions.map { |name, version| [name.to_s, { "url" => version.url }] }]
13
11
  end
14
12
 
15
13
  def as_json(options=nil)
16
- Hash[mounted_as || "uploader", serializable_hash]
14
+ serializable_hash
17
15
  end
18
16
 
19
17
  def to_json(options=nil)
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Uploader
5
3
  module Store
@@ -9,6 +7,15 @@ module CarrierWave
9
7
  include CarrierWave::Uploader::Configuration
10
8
  include CarrierWave::Uploader::Cache
11
9
 
10
+ included do
11
+ prepend Module.new {
12
+ def initialize(*)
13
+ super
14
+ @file, @filename, @cache_id, @identifier, @deduplication_index = nil
15
+ end
16
+ }
17
+ end
18
+
12
19
  ##
13
20
  # Override this in your Uploader to change the filename.
14
21
  #
@@ -27,9 +34,26 @@ module CarrierWave
27
34
  @filename
28
35
  end
29
36
 
37
+ ##
38
+ # Returns a filename which doesn't conflict with already-stored files.
39
+ #
40
+ # === Returns
41
+ #
42
+ # [String] the filename with suffix added for deduplication
43
+ #
44
+ def deduplicated_filename
45
+ return unless filename
46
+ return filename unless @deduplication_index
47
+
48
+ parts = filename.split('.')
49
+ basename = parts.shift
50
+ basename.sub!(/ ?\(\d+\)\z/, '')
51
+ ([basename.to_s + (@deduplication_index > 1 ? "(#{@deduplication_index})" : '')] + parts).join('.')
52
+ end
53
+
30
54
  ##
31
55
  # Calculates the path where the file should be stored. If +for_file+ is given, it will be
32
- # used as the filename, otherwise +CarrierWave::Uploader#filename+ is assumed.
56
+ # used as the identifier, otherwise +CarrierWave::Uploader#identifier+ is assumed.
33
57
  #
34
58
  # === Parameters
35
59
  #
@@ -39,7 +63,7 @@ module CarrierWave
39
63
  #
40
64
  # [String] the store path
41
65
  #
42
- def store_path(for_file=filename)
66
+ def store_path(for_file=identifier)
43
67
  File.join([store_dir, full_filename(for_file)].compact)
44
68
  end
45
69
 
@@ -53,32 +77,18 @@ module CarrierWave
53
77
  # [new_file (File, IOString, Tempfile)] any kind of file object
54
78
  #
55
79
  def store!(new_file=nil)
56
- cache!(new_file) if new_file && ((@cache_id != parent_cache_id) || @cache_id.nil?)
57
- if @file and @cache_id
80
+ cache!(new_file) if new_file && !cached?
81
+ if !cache_only && @file && @cache_id
58
82
  with_callbacks(:store, new_file) do
59
83
  new_file = storage.store!(@file)
60
- @file.delete if (delete_tmp_file_after_storage && ! move_to_store)
61
- delete_cache_id
84
+ if delete_tmp_file_after_storage
85
+ @file.delete unless move_to_store
86
+ cache_storage.delete_dir!(cache_path(nil))
87
+ end
62
88
  @file = new_file
63
- @cache_id = nil
64
- end
65
- end
66
- end
67
-
68
- ##
69
- # Deletes a cache id (tmp dir in cache)
70
- #
71
- def delete_cache_id
72
- if @cache_id
73
- path = File.expand_path(File.join(cache_dir, @cache_id), CarrierWave.root)
74
- begin
75
- Dir.rmdir(path)
76
- rescue Errno::ENOENT
77
- # Ignore: path does not exist
78
- rescue Errno::ENOTDIR
79
- # Ignore: path is not a dir
80
- rescue Errno::ENOTEMPTY, Errno::EEXIST
81
- # Ignore: dir is not empty
89
+ @identifier = storage.identifier
90
+ @cache_id = @deduplication_index = nil
91
+ @staged = false
82
92
  end
83
93
  end
84
94
  end
@@ -93,13 +103,34 @@ module CarrierWave
93
103
  def retrieve_from_store!(identifier)
94
104
  with_callbacks(:retrieve_from_store, identifier) do
95
105
  @file = storage.retrieve!(identifier)
106
+ @identifier = identifier
107
+ end
108
+ end
109
+
110
+ ##
111
+ # Look for an identifier which doesn't collide with the given already-stored identifiers.
112
+ # It is done by adding a index number as the suffix.
113
+ # For example, if there's 'image.jpg' and the @deduplication_index is set to 2,
114
+ # The stored file will be named as 'image(2).jpg'.
115
+ #
116
+ # === Parameters
117
+ #
118
+ # [current_identifiers (Array[String])] List of identifiers for already-stored files
119
+ #
120
+ def deduplicate(current_identifiers)
121
+ @deduplication_index = nil
122
+ return unless current_identifiers.include?(identifier)
123
+
124
+ (1..current_identifiers.size + 1).each do |i|
125
+ @deduplication_index = i
126
+ break unless current_identifiers.include?(identifier)
96
127
  end
97
128
  end
98
129
 
99
130
  private
100
131
 
101
132
  def full_filename(for_file)
102
- for_file
133
+ forcing_extension(for_file)
103
134
  end
104
135
 
105
136
  def storage
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Uploader
5
3
  module Url
@@ -17,12 +15,15 @@ module CarrierWave
17
15
  # [String] the location where this file is accessible via a url
18
16
  #
19
17
  def url(options = {})
20
- if file.respond_to?(:url) and not file.url.blank?
21
- file.method(:url).arity == 0 ? file.url : file.url(options)
22
- elsif file.respond_to?(:path)
23
- path = encode_path(file.path.gsub(File.expand_path(root), ''))
18
+ if file.respond_to?(:url)
19
+ tmp_url = file.method(:url).arity.zero? ? file.url : file.url(options)
20
+ return tmp_url if tmp_url.present?
21
+ end
22
+
23
+ if file.respond_to?(:path)
24
+ path = encode_path(file.path.sub(File.expand_path(root), ''))
24
25
 
25
- if host = asset_host
26
+ if (host = asset_host)
26
27
  if host.respond_to? :call
27
28
  "#{host.call(file)}#{path}"
28
29
  else