imagery 0.0.6 → 0.2.0
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.
- data/README.markdown +36 -63
- data/imagery.gemspec +8 -71
- data/lib/imagery.rb +176 -18
- data/lib/imagery/faking.rb +2 -2
- data/lib/imagery/s3.rb +68 -128
- data/lib/imagery/test.rb +5 -5
- data/test/faking.rb +71 -0
- data/test/helper.rb +28 -9
- data/test/imagery.rb +172 -0
- data/test/s3.rb +109 -0
- metadata +22 -91
- data/.document +0 -5
- data/.gitignore +0 -24
- data/Rakefile +0 -56
- data/VERSION +0 -1
- data/lib/imagery/missing.rb +0 -30
- data/lib/imagery/model.rb +0 -217
- data/test/fixtures/lake.jpg +0 -0
- data/test/test_imagery.rb +0 -172
- data/test/test_missing.rb +0 -30
- data/test/test_with_s3.rb +0 -167
data/lib/imagery/faking.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
class Imagery
|
2
2
|
module Faking
|
3
3
|
def self.included(base)
|
4
4
|
base.extend ClassMethods
|
@@ -73,7 +73,7 @@ module Imagery
|
|
73
73
|
|
74
74
|
# Implement the stubbed version of save and skips actual operation
|
75
75
|
# if Imagery::Model.mode == :fake
|
76
|
-
def save(io)
|
76
|
+
def save(io, key = nil)
|
77
77
|
return true if self.class.mode == :fake
|
78
78
|
|
79
79
|
super
|
data/lib/imagery/s3.rb
CHANGED
@@ -1,165 +1,106 @@
|
|
1
|
-
require
|
1
|
+
require "aws/s3"
|
2
2
|
|
3
|
-
|
3
|
+
class Imagery
|
4
4
|
module S3
|
5
|
-
def self.included(
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
def self.included(imagery)
|
6
|
+
imagery.extend Config
|
7
|
+
|
8
|
+
# Set the default host for amazon S3. You can also set this
|
9
|
+
# to https://s3.amazon.com if you want to force secure connections
|
10
|
+
# on a global scale.
|
11
|
+
imagery.s3_host "http://s3.amazonaws.com"
|
10
12
|
end
|
11
13
|
|
12
|
-
module
|
14
|
+
module Config
|
13
15
|
def s3_bucket(bucket = nil)
|
14
|
-
@s3_bucket = bucket
|
15
|
-
@s3_bucket
|
16
|
+
@s3_bucket = bucket if bucket
|
17
|
+
@s3_bucket
|
16
18
|
end
|
17
19
|
|
18
|
-
BUCKET_ERROR_MSG = (<<-MSG).gsub(/^ {6}/, '')
|
19
|
-
|
20
|
-
You need to define a bucket name. Example:
|
21
|
-
|
22
|
-
class Imagery::Model
|
23
|
-
include Imagery::S3
|
24
|
-
|
25
|
-
s3_bucket 'my-bucket-name'
|
26
|
-
end
|
27
|
-
MSG
|
28
|
-
|
29
20
|
def s3_distribution_domain(domain = nil)
|
30
|
-
@s3_distribution_domain = domain
|
21
|
+
@s3_distribution_domain = domain if domain
|
31
22
|
@s3_distribution_domain
|
32
23
|
end
|
33
|
-
|
34
|
-
# Allows you to customize the S3 host. Usually happens when you use
|
35
|
-
# amazon S3 EU.
|
36
|
-
#
|
37
|
-
# @param [String] host the custom host you want to use instead.
|
38
|
-
# @return [String] the s3 host currently set.
|
24
|
+
|
39
25
|
def s3_host(host = nil)
|
40
|
-
@s3_host = host
|
41
|
-
@s3_host
|
26
|
+
@s3_host = host if host
|
27
|
+
@s3_host
|
42
28
|
end
|
43
29
|
end
|
44
|
-
|
45
|
-
UndefinedBucket = Class.new(StandardError)
|
46
30
|
|
47
|
-
|
31
|
+
# Convenience attribute which returns all size keys including `:original`.
|
32
|
+
attr :keys
|
48
33
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
# end
|
59
|
-
#
|
60
|
-
# Photo = Class.new(Struct.new(:id))
|
61
|
-
# i = Imagery.new(Photo.new(1001))
|
62
|
-
#
|
63
|
-
# i.url == 'http://s3.amazonaws.com/bucket-name/photo/1001/original.png'
|
64
|
-
# # => true
|
65
|
-
#
|
66
|
-
# Imagery::Model.s3_distribution_domain = 'assets.site.com'
|
67
|
-
# i.url == 'http://assets.site.com/photo/1001/original.png'
|
68
|
-
# # => true
|
69
|
-
#
|
70
|
-
# # You may also subclass this of course since it's just a ruby object
|
71
|
-
# # and configure them differently as needed.
|
72
|
-
#
|
73
|
-
# class CloudFront < Imagery::Model
|
74
|
-
# include Imagery::S3
|
75
|
-
# s3_bucket 'cloudfront'
|
76
|
-
# s3_distribution_domain 'assets.site.com'
|
77
|
-
# end
|
78
|
-
#
|
79
|
-
# class RegularS3 < Imagery::Model
|
80
|
-
# include Imagery::S3
|
81
|
-
# s3_bucket 'cloudfront'
|
82
|
-
# end
|
34
|
+
def initialize(*args)
|
35
|
+
super
|
36
|
+
|
37
|
+
@keys = [@original] + sizes.keys
|
38
|
+
end
|
39
|
+
|
40
|
+
# If you specify a distribution domain (i.e. a cloudfront domain,
|
41
|
+
# or even an S3 domain with a prefix), that distribution domain is
|
42
|
+
# used.
|
83
43
|
#
|
84
|
-
#
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
[domain, namespace, key, filename(size)].join('/')
|
44
|
+
# Otherwise the default canonical S3 url is used.
|
45
|
+
def url(file = @original)
|
46
|
+
if self.class.s3_distribution_domain
|
47
|
+
"#{self.class.s3_distribution_domain}#{super}"
|
89
48
|
else
|
90
|
-
|
49
|
+
"#{self.class.s3_host}/#{self.class.s3_bucket}#{super}"
|
91
50
|
end
|
92
51
|
end
|
93
|
-
|
94
|
-
#
|
95
|
-
# the
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
:content_type => "image/png"
|
104
|
-
)
|
105
|
-
end
|
106
|
-
end
|
52
|
+
|
53
|
+
# Returns the complete S3 key used for this object. The S3 key
|
54
|
+
# is simply composed of the prefix and filename, e.g.
|
55
|
+
#
|
56
|
+
# - photos/1001/original.jpg
|
57
|
+
# - photos/1001/small.jpg
|
58
|
+
# - photos/1001/tiny.jpg
|
59
|
+
#
|
60
|
+
def s3_key(file)
|
61
|
+
"#{prefix}/#{key}/#{ext(file)}"
|
107
62
|
end
|
108
|
-
|
109
|
-
# Deletes all
|
110
|
-
# all
|
63
|
+
|
64
|
+
# Deletes all keys defined for this object, which includes `:original`
|
65
|
+
# and all keys in `sizes`.
|
111
66
|
def delete
|
112
67
|
super
|
113
|
-
|
114
|
-
|
68
|
+
|
69
|
+
keys.each do |file|
|
70
|
+
Gateway.delete(s3_key(file), self.class.s3_bucket)
|
115
71
|
end
|
116
72
|
end
|
73
|
+
|
74
|
+
# Save the object as we normall would, but also upload all resulting
|
75
|
+
# files to S3. We set the proper content type and Cache-Control setting
|
76
|
+
# optimized for a cloudfront setup.
|
77
|
+
def save(io, key = nil)
|
78
|
+
super
|
117
79
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
80
|
+
keys.each do |file|
|
81
|
+
Gateway.store(s3_key(file),
|
82
|
+
File.open(root(ext(file))),
|
83
|
+
self.class.s3_bucket,
|
84
|
+
:access => :public_read,
|
85
|
+
:content_type => "image/jpeg",
|
86
|
+
"Cache-Control" => "max-age=315360000"
|
87
|
+
)
|
122
88
|
end
|
123
89
|
end
|
124
|
-
|
90
|
+
|
91
|
+
# Provides a convenience wrapper around AWS::S3::S3Object and
|
92
|
+
# serves as an auto-connect module.
|
125
93
|
module Gateway
|
126
|
-
|
127
|
-
# using the environment vars.
|
128
|
-
#
|
129
|
-
# @example
|
130
|
-
#
|
131
|
-
# AWS::S3::Base.connected?
|
132
|
-
# # => false
|
133
|
-
#
|
134
|
-
# Imagery::S3::Gateway.store(
|
135
|
-
# 'avatar', File.open('avatar.jpg'), 'bucket'
|
136
|
-
# )
|
137
|
-
# AWS::S3::Base.connected?
|
138
|
-
# # => true
|
139
|
-
#
|
140
|
-
def store(*args)
|
94
|
+
def self.store(*args)
|
141
95
|
execute(:store, *args)
|
142
96
|
end
|
143
|
-
module_function :store
|
144
97
|
|
145
|
-
|
146
|
-
# using the environment vars.
|
147
|
-
#
|
148
|
-
# @example
|
149
|
-
#
|
150
|
-
# AWS::S3::Base.connected?
|
151
|
-
# # => false
|
152
|
-
#
|
153
|
-
# Imagery::S3::Gateway.delete('avatar', 'bucket')
|
154
|
-
# AWS::S3::Base.connected?
|
155
|
-
# # => true
|
156
|
-
def delete(*args)
|
98
|
+
def self.delete(*args)
|
157
99
|
execute(:delete, *args)
|
158
100
|
end
|
159
|
-
module_function :delete
|
160
101
|
|
161
102
|
private
|
162
|
-
def execute(command, *args)
|
103
|
+
def self.execute(command, *args)
|
163
104
|
begin
|
164
105
|
AWS::S3::S3Object.__send__(command, *args)
|
165
106
|
rescue AWS::S3::NoConnectionEstablished
|
@@ -170,7 +111,6 @@ module Imagery
|
|
170
111
|
retry
|
171
112
|
end
|
172
113
|
end
|
173
|
-
module_function :execute
|
174
114
|
end
|
175
115
|
end
|
176
116
|
end
|
data/lib/imagery/test.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
|
1
|
+
class Imagery
|
2
2
|
module Test
|
3
3
|
def self.included(base)
|
4
|
-
Imagery
|
5
|
-
Imagery
|
4
|
+
Imagery.send :include, Imagery::Faking
|
5
|
+
Imagery.mode = :fake
|
6
6
|
end
|
7
7
|
|
8
8
|
protected
|
9
9
|
def imagery
|
10
10
|
if ENV["REAL_IMAGERY"]
|
11
|
-
Imagery
|
11
|
+
Imagery.real { yield true }
|
12
12
|
else
|
13
|
-
Imagery
|
13
|
+
Imagery.faked { yield false }
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
data/test/faking.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
require "stringio"
|
3
|
+
|
4
|
+
# Imagery::Test hooks
|
5
|
+
scope do
|
6
|
+
setup do
|
7
|
+
Object.send :include, Imagery::Test
|
8
|
+
end
|
9
|
+
|
10
|
+
test "auto-includes Imagery::Faking upon inclusion" do
|
11
|
+
assert Imagery.ancestors.include?(Imagery::Faking)
|
12
|
+
end
|
13
|
+
|
14
|
+
test "auto-sets mode to :fake" do
|
15
|
+
assert_equal :fake, Imagery.mode
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
class Imagery
|
21
|
+
include Faking
|
22
|
+
end
|
23
|
+
|
24
|
+
test "skips saving when faked" do
|
25
|
+
Imagery.mode = :fake
|
26
|
+
|
27
|
+
i = Imagery.new(:avatar, "1001")
|
28
|
+
i.save(StringIO.new)
|
29
|
+
|
30
|
+
assert ! File.exist?(Imagery.root("avatar", "1001"))
|
31
|
+
end
|
32
|
+
|
33
|
+
test "skips deleting when faked" do
|
34
|
+
Imagery.mode = :fake
|
35
|
+
|
36
|
+
FileUtils.mkdir_p(Imagery.root("avatar", "1001"))
|
37
|
+
|
38
|
+
i = Imagery.new(:avatar, "1001")
|
39
|
+
i.delete
|
40
|
+
|
41
|
+
assert File.exist?(Imagery.root("avatar", "1001"))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Imagery::Test
|
45
|
+
scope do
|
46
|
+
extend Imagery::Test
|
47
|
+
|
48
|
+
test "yields true when REAL_IMAGERY is set" do
|
49
|
+
ENV["REAL_IMAGERY"] = "true"
|
50
|
+
|
51
|
+
enabled = nil
|
52
|
+
|
53
|
+
imagery do |e|
|
54
|
+
enabled = e
|
55
|
+
end
|
56
|
+
|
57
|
+
assert enabled
|
58
|
+
end
|
59
|
+
|
60
|
+
test "yields false when REAL_IMAGERY is not set" do
|
61
|
+
ENV["REAL_IMAGERY"] = nil
|
62
|
+
|
63
|
+
enabled = nil
|
64
|
+
|
65
|
+
imagery do |e|
|
66
|
+
enabled = e
|
67
|
+
end
|
68
|
+
|
69
|
+
assert_equal false, enabled
|
70
|
+
end
|
71
|
+
end
|
data/test/helper.rb
CHANGED
@@ -1,12 +1,31 @@
|
|
1
|
-
|
2
|
-
require 'test/unit'
|
3
|
-
require 'contest'
|
4
|
-
require 'mocha'
|
1
|
+
$:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
require 'imagery'
|
3
|
+
require "cutest"
|
4
|
+
require "imagery"
|
9
5
|
|
10
|
-
|
11
|
-
|
6
|
+
def fixture(filename)
|
7
|
+
File.expand_path("fixtures/#{filename}", File.dirname(__FILE__))
|
8
|
+
end
|
9
|
+
|
10
|
+
def resolution(file)
|
11
|
+
`gm identify #{file}`[/(\d+x\d+)/, 1]
|
12
|
+
end
|
13
|
+
|
14
|
+
prepare do
|
15
|
+
FileUtils.rm_rf(File.expand_path("../public", File.dirname(__FILE__)))
|
16
|
+
Imagery::S3::Gateway.commands.clear
|
17
|
+
end
|
18
|
+
|
19
|
+
class Imagery
|
20
|
+
module S3
|
21
|
+
module Gateway
|
22
|
+
def self.execute(command, *args)
|
23
|
+
commands << [command, *args.select { |a| a.respond_to?(:to_str) }]
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.commands
|
27
|
+
@commands ||= []
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
12
31
|
end
|
data/test/imagery.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
require File.expand_path("helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
test "defining with a prefix" do
|
4
|
+
i = Imagery.new(:avatar)
|
5
|
+
|
6
|
+
assert_equal "avatar", i.prefix
|
7
|
+
end
|
8
|
+
|
9
|
+
test "defining with a key" do
|
10
|
+
i = Imagery.new(:avatar, "1001")
|
11
|
+
|
12
|
+
assert_equal "avatar", i.prefix
|
13
|
+
assert_equal "1001", i.key
|
14
|
+
end
|
15
|
+
|
16
|
+
test "defining with sizes" do
|
17
|
+
i = Imagery.new(:avatar, "1001", small: ["100x100"])
|
18
|
+
|
19
|
+
assert_equal "avatar", i.prefix
|
20
|
+
assert_equal "1001", i.key
|
21
|
+
assert_equal({ small: ["100x100"] }, i.sizes)
|
22
|
+
end
|
23
|
+
|
24
|
+
test "root defined to be Dir.pwd/public" do
|
25
|
+
assert_equal File.join(Dir.pwd, "public"), Imagery.root
|
26
|
+
end
|
27
|
+
|
28
|
+
test "root accepts arguments" do
|
29
|
+
assert_equal File.join(Dir.pwd, "public", "tmp"), Imagery.root("tmp")
|
30
|
+
end
|
31
|
+
|
32
|
+
test "allows override of the default Dir.pwd" do
|
33
|
+
begin
|
34
|
+
tmp = File.expand_path("tmp", File.dirname(__FILE__))
|
35
|
+
|
36
|
+
Imagery.root = tmp
|
37
|
+
assert_equal tmp, Imagery.root
|
38
|
+
|
39
|
+
ensure
|
40
|
+
Imagery.root = File.join(Dir.pwd, "public")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
test "url when missing key" do
|
45
|
+
i = Imagery.new(:avatar)
|
46
|
+
|
47
|
+
assert_equal "/missing/avatar/original.jpg", i.url
|
48
|
+
end
|
49
|
+
|
50
|
+
test "url with a key" do
|
51
|
+
i = Imagery.new(:avatar, "1001")
|
52
|
+
assert_equal "/avatar/1001/original.jpg", i.url
|
53
|
+
end
|
54
|
+
|
55
|
+
test "url with a key and a file" do
|
56
|
+
i = Imagery.new(:avatar, "1001")
|
57
|
+
assert_equal "/avatar/1001/small.jpg", i.url(:small)
|
58
|
+
end
|
59
|
+
|
60
|
+
# basic persistence
|
61
|
+
scope do
|
62
|
+
setup do
|
63
|
+
imagery = Imagery.new(:avatar, "1001")
|
64
|
+
io = File.open(fixture("r8.jpg"), "rb")
|
65
|
+
|
66
|
+
[imagery, io]
|
67
|
+
end
|
68
|
+
|
69
|
+
test "saving without any sizes defined" do |im, io|
|
70
|
+
assert im.save(io)
|
71
|
+
|
72
|
+
assert File.exist?(im.root("original.jpg"))
|
73
|
+
assert_equal "1024x768", resolution(im.root("original.jpg"))
|
74
|
+
end
|
75
|
+
|
76
|
+
test "saving and specifying the key" do |im, io|
|
77
|
+
assert im.save(io, "GUID")
|
78
|
+
assert File.exist?(Imagery.root("avatar/GUID/original.jpg"))
|
79
|
+
end
|
80
|
+
|
81
|
+
test "saving with an already existing image" do |im, io|
|
82
|
+
im.save(io)
|
83
|
+
|
84
|
+
assert im.save(io, "GUID")
|
85
|
+
assert File.exist?(Imagery.root("avatar/GUID/original.jpg"))
|
86
|
+
assert ! File.exist?(Imagery.root("avatar/1001/original.jpg"))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# basic resizing
|
91
|
+
scope do
|
92
|
+
setup do
|
93
|
+
imagery = Imagery.new(:avatar, 1, small: ["100x100"], tiny: ["30x30"])
|
94
|
+
io = File.open(fixture("r8.jpg"), "rb")
|
95
|
+
|
96
|
+
[imagery, io]
|
97
|
+
end
|
98
|
+
|
99
|
+
test "saves the different sizes" do |im, io|
|
100
|
+
assert im.save(io)
|
101
|
+
|
102
|
+
assert File.exist?(im.root("original.jpg"))
|
103
|
+
assert File.exist?(im.root("small.jpg"))
|
104
|
+
assert File.exist?(im.root("tiny.jpg"))
|
105
|
+
|
106
|
+
assert_equal "1024x768", resolution(im.root("original.jpg"))
|
107
|
+
|
108
|
+
# Since there was no extent or geometry specified, this will
|
109
|
+
# be resized by fitting the image proportionally within 100x100.
|
110
|
+
assert_equal "100x75", resolution(im.root("small.jpg"))
|
111
|
+
|
112
|
+
# Like small.jpg, it will be resized to fit within 30x30
|
113
|
+
assert_equal "30x23", resolution(im.root("tiny.jpg"))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# resizing with extent
|
118
|
+
scope do
|
119
|
+
setup do
|
120
|
+
imagery = Imagery.new(:avatar, 1, small: ["100x100^", "100x100"])
|
121
|
+
io = File.open(fixture("r8.jpg"), "rb")
|
122
|
+
|
123
|
+
[imagery, io]
|
124
|
+
end
|
125
|
+
|
126
|
+
test "saves an image maximized within the extent" do |im, io|
|
127
|
+
im.save(io)
|
128
|
+
|
129
|
+
assert_equal "100x100", resolution(im.root("small.jpg"))
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# saving a corrupted / unrecognized file
|
134
|
+
scope do
|
135
|
+
setup do
|
136
|
+
imagery = Imagery.new(:avatar, 1, small: ["100x100^", "100x100"])
|
137
|
+
io = File.open(fixture("r8.jpg"), "rb")
|
138
|
+
|
139
|
+
[imagery, io]
|
140
|
+
end
|
141
|
+
|
142
|
+
test "creating a new image" do |im, io|
|
143
|
+
ex = nil
|
144
|
+
|
145
|
+
begin
|
146
|
+
im.save(File.open(fixture("broken.jpg"), "rb"))
|
147
|
+
rescue Imagery::InvalidImage => e
|
148
|
+
ex = e
|
149
|
+
end
|
150
|
+
|
151
|
+
assert ex
|
152
|
+
|
153
|
+
o, s = im.root("original.jpg"), im.root("small.jpg")
|
154
|
+
|
155
|
+
assert ! File.exist?(o)
|
156
|
+
assert ! File.exist?(s)
|
157
|
+
end
|
158
|
+
|
159
|
+
test "trying to save over an existing image" do |im, io|
|
160
|
+
im.save(io)
|
161
|
+
|
162
|
+
o, s = im.root("original.jpg"), im.root("small.jpg")
|
163
|
+
|
164
|
+
begin
|
165
|
+
im.save(File.open(fixture("broken.jpg"), "rb"), 2)
|
166
|
+
rescue Imagery::InvalidImage
|
167
|
+
end
|
168
|
+
|
169
|
+
assert File.exist?(o)
|
170
|
+
assert File.exist?(s)
|
171
|
+
end
|
172
|
+
end
|