paperclip 3.0.4 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of paperclip might be problematic. Click here for more details.
- data/Appraisals +3 -3
- data/NEWS +43 -0
- data/README.md +81 -5
- data/features/basic_integration.feature +20 -2
- data/features/migration.feature +94 -0
- data/features/step_definitions/attachment_steps.rb +28 -0
- data/features/step_definitions/rails_steps.rb +19 -1
- data/features/step_definitions/web_steps.rb +3 -3
- data/gemfiles/3.0.gemfile +1 -1
- data/gemfiles/3.1.gemfile +1 -1
- data/gemfiles/3.2.gemfile +1 -1
- data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +4 -8
- data/lib/paperclip.rb +2 -0
- data/lib/paperclip/attachment.rb +4 -0
- data/lib/paperclip/geometry.rb +33 -0
- data/lib/paperclip/glue.rb +2 -1
- data/lib/paperclip/io_adapters/abstract_adapter.rb +45 -0
- data/lib/paperclip/io_adapters/attachment_adapter.rb +13 -48
- data/lib/paperclip/io_adapters/file_adapter.rb +11 -61
- data/lib/paperclip/io_adapters/identity_adapter.rb +1 -1
- data/lib/paperclip/io_adapters/nil_adapter.rb +1 -1
- data/lib/paperclip/io_adapters/stringio_adapter.rb +11 -42
- data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +6 -45
- data/lib/paperclip/matchers.rb +2 -2
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +36 -17
- data/lib/paperclip/railtie.rb +5 -1
- data/lib/paperclip/schema.rb +59 -23
- data/lib/paperclip/storage/filesystem.rb +5 -0
- data/lib/paperclip/storage/fog.rb +36 -14
- data/lib/paperclip/storage/s3.rb +14 -16
- data/lib/paperclip/style.rb +2 -2
- data/lib/paperclip/tempfile_factory.rb +21 -0
- data/lib/paperclip/thumbnail.rb +10 -1
- data/lib/paperclip/version.rb +1 -1
- data/paperclip.gemspec +1 -1
- data/test/attachment_test.rb +56 -24
- data/test/fixtures/animated +0 -0
- data/test/fixtures/animated.unknown +0 -0
- data/test/generator_test.rb +26 -24
- data/test/geometry_test.rb +19 -0
- data/test/helper.rb +8 -0
- data/test/integration_test.rb +23 -23
- data/test/io_adapters/abstract_adapter_test.rb +44 -0
- data/test/io_adapters/attachment_adapter_test.rb +96 -34
- data/test/io_adapters/file_adapter_test.rb +13 -1
- data/test/io_adapters/stringio_adapter_test.rb +9 -10
- data/test/io_adapters/uploaded_file_adapter_test.rb +2 -1
- data/test/schema_test.rb +179 -77
- data/test/storage/filesystem_test.rb +18 -3
- data/test/storage/fog_test.rb +64 -1
- data/test/storage/s3_test.rb +38 -2
- data/test/tempfile_factory_test.rb +13 -0
- data/test/thumbnail_test.rb +45 -0
- metadata +16 -9
- data/features/support/fixtures/.boot_config.rb.swo +0 -0
- data/images.rake +0 -21
data/lib/paperclip/attachment.rb
CHANGED
@@ -440,6 +440,10 @@ module Paperclip
|
|
440
440
|
|
441
441
|
# called by storage after the writes are flushed and before @queued_for_writes is cleared
|
442
442
|
def after_flush_writes
|
443
|
+
@queued_for_write.each do |style, file|
|
444
|
+
file.close unless file.closed?
|
445
|
+
file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path)
|
446
|
+
end
|
443
447
|
end
|
444
448
|
|
445
449
|
def cleanup_filename(filename)
|
data/lib/paperclip/geometry.rb
CHANGED
@@ -101,6 +101,33 @@ module Paperclip
|
|
101
101
|
[ scale_geometry, crop_geometry ]
|
102
102
|
end
|
103
103
|
|
104
|
+
# resize to a new geometry
|
105
|
+
# @param geometry [String] the Paperclip geometry definition to resize to
|
106
|
+
# @example
|
107
|
+
# Paperclip::Geometry.new(150, 150).resize_to('50x50!')
|
108
|
+
# #=> Paperclip::Geometry(50, 50)
|
109
|
+
def resize_to(geometry)
|
110
|
+
new_geometry = Paperclip::Geometry.parse geometry
|
111
|
+
case new_geometry.modifier
|
112
|
+
when '!', '#'
|
113
|
+
new_geometry
|
114
|
+
when '>'
|
115
|
+
if new_geometry.width >= self.width && new_geometry.height >= self.height
|
116
|
+
self
|
117
|
+
else
|
118
|
+
scale_to new_geometry
|
119
|
+
end
|
120
|
+
when '<'
|
121
|
+
if new_geometry.width <= self.width || new_geometry.height <= self.height
|
122
|
+
self
|
123
|
+
else
|
124
|
+
scale_to new_geometry
|
125
|
+
end
|
126
|
+
else
|
127
|
+
scale_to new_geometry
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
104
131
|
private
|
105
132
|
|
106
133
|
def scaling dst, ratio
|
@@ -118,5 +145,11 @@ module Paperclip
|
|
118
145
|
"%dx%d+%d+%d" % [ dst.width, dst.height, (self.width * scale - dst.width) / 2, 0 ]
|
119
146
|
end
|
120
147
|
end
|
148
|
+
|
149
|
+
# scale to the requested geometry and preserve the aspect ratio
|
150
|
+
def scale_to(new_geometry)
|
151
|
+
scale = [new_geometry.width.to_f / self.width.to_f , new_geometry.height.to_f / self.height.to_f].min
|
152
|
+
Paperclip::Geometry.new (self.width * scale).round, (self.height * scale).round
|
153
|
+
end
|
121
154
|
end
|
122
155
|
end
|
data/lib/paperclip/glue.rb
CHANGED
@@ -4,10 +4,11 @@ require 'paperclip/schema'
|
|
4
4
|
|
5
5
|
module Paperclip
|
6
6
|
module Glue
|
7
|
-
def self.included
|
7
|
+
def self.included(base)
|
8
8
|
base.extend ClassMethods
|
9
9
|
base.send :include, Callbacks
|
10
10
|
base.send :include, Validators
|
11
|
+
base.send :include, Schema
|
11
12
|
base.class_attribute :attachment_definitions
|
12
13
|
|
13
14
|
if defined?(ActiveRecord)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
|
3
|
+
module Paperclip
|
4
|
+
class AbstractAdapter
|
5
|
+
attr_reader :content_type, :original_filename, :size
|
6
|
+
delegate :close, :closed?, :eof?, :path, :rewind, :unlink, :to => :@tempfile
|
7
|
+
|
8
|
+
def fingerprint
|
9
|
+
@fingerprint ||= Digest::MD5.file(path).to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def read(length = nil, buffer = nil)
|
13
|
+
@tempfile.read(length, buffer)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def destination
|
19
|
+
@destination ||= TempfileFactory.new.generate(original_filename)
|
20
|
+
end
|
21
|
+
|
22
|
+
def copy_to_tempfile(src)
|
23
|
+
FileUtils.cp(src.path, destination.path)
|
24
|
+
destination
|
25
|
+
end
|
26
|
+
|
27
|
+
def best_content_type_option(types)
|
28
|
+
best = types.reject {|type| type.content_type.match(/\/x-/) }
|
29
|
+
if best.size == 0
|
30
|
+
types.first.content_type
|
31
|
+
else
|
32
|
+
best.first.content_type
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def type_from_file_command
|
37
|
+
# On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
|
38
|
+
type = (File.extname(self.path.to_s)).downcase
|
39
|
+
type = "octet-stream" if type.empty?
|
40
|
+
mime_type = Paperclip.run("file", "-b --mime :file", :file => self.path).split(/[:;\s]+/)[0]
|
41
|
+
mime_type = "application/x-#{type}" if mime_type.match(/\(.*?\)/)
|
42
|
+
mime_type
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,71 +1,36 @@
|
|
1
1
|
module Paperclip
|
2
|
-
class AttachmentAdapter
|
2
|
+
class AttachmentAdapter < AbstractAdapter
|
3
3
|
def initialize(target)
|
4
|
-
@target = target
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
11
|
-
|
12
|
-
def content_type
|
13
|
-
@content_type
|
14
|
-
end
|
15
|
-
|
16
|
-
def size
|
17
|
-
@size
|
18
|
-
end
|
19
|
-
|
20
|
-
def nil?
|
21
|
-
false
|
22
|
-
end
|
23
|
-
|
24
|
-
def fingerprint
|
25
|
-
@fingerprint ||= Digest::MD5.file(path).to_s
|
26
|
-
end
|
27
|
-
|
28
|
-
def read(length = nil, buffer = nil)
|
29
|
-
@tempfile.read(length, buffer)
|
30
|
-
end
|
31
|
-
|
32
|
-
# We don't use this directly, but aws/sdk does.
|
33
|
-
def rewind
|
34
|
-
@tempfile.rewind
|
35
|
-
end
|
36
|
-
|
37
|
-
def eof?
|
38
|
-
@tempfile.eof?
|
39
|
-
end
|
4
|
+
@target, @style = case target
|
5
|
+
when Paperclip::Attachment
|
6
|
+
[target, :original]
|
7
|
+
when Paperclip::Style
|
8
|
+
[target.attachment, target.name]
|
9
|
+
end
|
40
10
|
|
41
|
-
|
42
|
-
@tempfile.path
|
11
|
+
cache_current_values
|
43
12
|
end
|
44
13
|
|
45
14
|
private
|
46
15
|
|
47
16
|
def cache_current_values
|
48
|
-
@tempfile = copy_to_tempfile(@target)
|
49
17
|
@original_filename = @target.original_filename
|
50
18
|
@content_type = @target.content_type
|
19
|
+
@tempfile = copy_to_tempfile(@target)
|
51
20
|
@size = @tempfile.size || @target.size
|
52
21
|
end
|
53
22
|
|
54
23
|
def copy_to_tempfile(src)
|
55
|
-
extension = File.extname(src.original_filename)
|
56
|
-
basename = File.basename(src.original_filename, extension)
|
57
|
-
dest = Tempfile.new([basename, extension])
|
58
|
-
dest.binmode
|
59
24
|
if src.respond_to? :copy_to_local_file
|
60
|
-
src.copy_to_local_file(
|
25
|
+
src.copy_to_local_file(@style, destination.path)
|
61
26
|
else
|
62
|
-
FileUtils.cp(src.path(
|
27
|
+
FileUtils.cp(src.path(@style), destination.path)
|
63
28
|
end
|
64
|
-
|
29
|
+
destination
|
65
30
|
end
|
66
31
|
end
|
67
32
|
end
|
68
33
|
|
69
34
|
Paperclip.io_adapters.register Paperclip::AttachmentAdapter do |target|
|
70
|
-
Paperclip::Attachment === target
|
35
|
+
Paperclip::Attachment === target || Paperclip::Style === target
|
71
36
|
end
|
@@ -1,19 +1,21 @@
|
|
1
1
|
module Paperclip
|
2
|
-
class FileAdapter
|
2
|
+
class FileAdapter < AbstractAdapter
|
3
3
|
def initialize(target)
|
4
4
|
@target = target
|
5
|
-
|
5
|
+
cache_current_values
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
private
|
9
|
+
|
10
|
+
def cache_current_values
|
11
|
+
@original_filename = @target.original_filename if @target.respond_to?(:original_filename)
|
12
|
+
@original_filename ||= File.basename(@target.path)
|
13
|
+
@tempfile = copy_to_tempfile(@target)
|
14
|
+
@content_type = calculate_content_type
|
15
|
+
@size = File.size(@target)
|
14
16
|
end
|
15
17
|
|
16
|
-
def
|
18
|
+
def calculate_content_type
|
17
19
|
types = MIME::Types.type_for(original_filename)
|
18
20
|
if types.length == 0
|
19
21
|
type_from_file_command
|
@@ -23,58 +25,6 @@ module Paperclip
|
|
23
25
|
best_content_type_option(types)
|
24
26
|
end
|
25
27
|
end
|
26
|
-
|
27
|
-
def fingerprint
|
28
|
-
@fingerprint ||= Digest::MD5.file(path).to_s
|
29
|
-
end
|
30
|
-
|
31
|
-
def size
|
32
|
-
File.size(@tempfile)
|
33
|
-
end
|
34
|
-
|
35
|
-
def nil?
|
36
|
-
@target.nil?
|
37
|
-
end
|
38
|
-
|
39
|
-
def read(length = nil, buffer = nil)
|
40
|
-
@tempfile.read(length, buffer)
|
41
|
-
end
|
42
|
-
|
43
|
-
# We don't use this directly, but aws/sdk does.
|
44
|
-
def rewind
|
45
|
-
@tempfile.rewind
|
46
|
-
end
|
47
|
-
|
48
|
-
def eof?
|
49
|
-
@tempfile.eof?
|
50
|
-
end
|
51
|
-
|
52
|
-
def path
|
53
|
-
@tempfile.path
|
54
|
-
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
def copy_to_tempfile(src)
|
59
|
-
extension = File.extname(original_filename)
|
60
|
-
basename = File.basename(original_filename, extension)
|
61
|
-
dest = Tempfile.new([basename, extension])
|
62
|
-
dest.binmode
|
63
|
-
FileUtils.cp(src.path, dest.path)
|
64
|
-
dest
|
65
|
-
end
|
66
|
-
|
67
|
-
def best_content_type_option(types)
|
68
|
-
types.reject {|type| type.content_type.match(/\/x-/) }.first.content_type
|
69
|
-
end
|
70
|
-
|
71
|
-
def type_from_file_command
|
72
|
-
# On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
|
73
|
-
type = (self.original_filename.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
|
74
|
-
mime_type = (Paperclip.run("file", "-b --mime :file", :file => self.path).split(/[:;\s]+/)[0] rescue "application/x-#{type}")
|
75
|
-
mime_type = "application/x-#{type}" if mime_type.match(/\(.*?\)/)
|
76
|
-
mime_type
|
77
|
-
end
|
78
28
|
end
|
79
29
|
end
|
80
30
|
|
@@ -1,61 +1,30 @@
|
|
1
1
|
module Paperclip
|
2
|
-
class StringioAdapter
|
2
|
+
class StringioAdapter < AbstractAdapter
|
3
3
|
def initialize(target)
|
4
4
|
@target = target
|
5
|
+
cache_current_values
|
5
6
|
@tempfile = copy_to_tempfile(@target)
|
6
7
|
end
|
7
8
|
|
8
|
-
|
9
|
+
private
|
9
10
|
|
10
|
-
def
|
11
|
-
@original_filename
|
11
|
+
def cache_current_values
|
12
|
+
@original_filename = @target.original_filename if @target.respond_to?(:original_filename)
|
12
13
|
@original_filename ||= "stringio.txt"
|
13
|
-
@original_filename.strip
|
14
|
-
end
|
14
|
+
@original_filename = @original_filename.strip
|
15
15
|
|
16
|
-
|
17
|
-
@content_type ||= @target.content_type if @target.respond_to?(:content_type)
|
16
|
+
@content_type = @target.content_type if @target.respond_to?(:content_type)
|
18
17
|
@content_type ||= "text/plain"
|
19
|
-
@content_type
|
20
|
-
end
|
21
|
-
|
22
|
-
def size
|
23
|
-
@target.size
|
24
|
-
end
|
25
|
-
|
26
|
-
def fingerprint
|
27
|
-
Digest::MD5.hexdigest(read)
|
28
|
-
end
|
29
18
|
|
30
|
-
|
31
|
-
@tempfile.read(length, buffer)
|
19
|
+
@size = @target.size
|
32
20
|
end
|
33
21
|
|
34
|
-
# We don't use this directly, but aws/sdk does.
|
35
|
-
def rewind
|
36
|
-
@tempfile.rewind
|
37
|
-
end
|
38
|
-
|
39
|
-
def eof?
|
40
|
-
@tempfile.eof?
|
41
|
-
end
|
42
|
-
|
43
|
-
def path
|
44
|
-
@tempfile.path
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
22
|
def copy_to_tempfile(src)
|
50
|
-
extension = File.extname(original_filename)
|
51
|
-
basename = File.basename(original_filename, extension)
|
52
|
-
dest = Tempfile.new([basename, extension])
|
53
|
-
dest.binmode
|
54
23
|
while data = src.read(16*1024)
|
55
|
-
|
24
|
+
destination.write(data)
|
56
25
|
end
|
57
|
-
|
58
|
-
|
26
|
+
destination.rewind
|
27
|
+
destination
|
59
28
|
end
|
60
29
|
|
61
30
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
module Paperclip
|
2
|
-
class UploadedFileAdapter
|
2
|
+
class UploadedFileAdapter < AbstractAdapter
|
3
3
|
def initialize(target)
|
4
4
|
@target = target
|
5
|
+
cache_current_values
|
5
6
|
|
6
7
|
if @target.respond_to?(:tempfile)
|
7
8
|
@tempfile = copy_to_tempfile(@target.tempfile)
|
@@ -10,52 +11,12 @@ module Paperclip
|
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
|
-
def original_filename
|
14
|
-
@target.original_filename
|
15
|
-
end
|
16
|
-
|
17
|
-
def content_type
|
18
|
-
@target.content_type
|
19
|
-
end
|
20
|
-
|
21
|
-
def fingerprint
|
22
|
-
@fingerprint ||= Digest::MD5.file(path).to_s
|
23
|
-
end
|
24
|
-
|
25
|
-
def size
|
26
|
-
File.size(path)
|
27
|
-
end
|
28
|
-
|
29
|
-
def nil?
|
30
|
-
false
|
31
|
-
end
|
32
|
-
|
33
|
-
def read(length = nil, buffer = nil)
|
34
|
-
@tempfile.read(length, buffer)
|
35
|
-
end
|
36
|
-
|
37
|
-
# We don't use this directly, but aws/sdk does.
|
38
|
-
def rewind
|
39
|
-
@tempfile.rewind
|
40
|
-
end
|
41
|
-
|
42
|
-
def eof?
|
43
|
-
@tempfile.eof?
|
44
|
-
end
|
45
|
-
|
46
|
-
def path
|
47
|
-
@tempfile.path
|
48
|
-
end
|
49
|
-
|
50
14
|
private
|
51
15
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
dest.binmode
|
57
|
-
FileUtils.cp(src.path, dest.path)
|
58
|
-
dest
|
16
|
+
def cache_current_values
|
17
|
+
@original_filename = @target.original_filename
|
18
|
+
@content_type = @target.content_type
|
19
|
+
@size = File.size(@target.path)
|
59
20
|
end
|
60
21
|
end
|
61
22
|
end
|