mugshot 0.2.0 → 0.3.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.md CHANGED
@@ -25,7 +25,7 @@ For production use, don't even think about using Mugshot without something like
25
25
  Varnish or Squid sitting in front.
26
26
 
27
27
 
28
- Supported Operations
28
+ Supported operations
29
29
  --------------------
30
30
 
31
31
  ### Resize
@@ -0,0 +1,8 @@
1
+ Feature: Crop image
2
+
3
+ Scenario: Crop image with known aspect ratio
4
+ When I upload an image
5
+
6
+ And I ask for the 300x200 cropped image
7
+
8
+ Then I should get the 300x200 cropped image
@@ -1,15 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  When /^I upload an image$/ do
2
3
  post '/', "file" => Rack::Test::UploadedFile.new("features/support/files/test.jpg", "image/jpeg")
3
4
  @image_id = last_response.body
4
5
  end
5
6
 
6
7
  When /^I ask for the (.*) resized image$/ do |size|
7
- get "/#{size}/#{@image_id}.jpg"
8
+ get "/resize/#{size}/#{@image_id}.jpg"
8
9
  @retrieved_image = last_response.body
9
10
  end
10
11
 
11
12
  When /^I ask for a (.*) resized image that doesn't exist$/ do |size|
12
- get "/#{size}/nonexistant.jpg"
13
+ get "/resize/#{size}/nonexistant.jpg"
14
+ end
15
+
16
+ When /^I ask for the (.*) cropped image$/ do |size|
17
+ get "/crop/#{size}/#{@image_id}.jpg"
18
+ @retrieved_image = last_response.body
13
19
  end
14
20
 
15
21
  Then /^I should get a (\d+) response$/ do |response_code|
@@ -23,3 +29,7 @@ end
23
29
  Then /^I should get the (.*) resized image keeping the aspect ratio$/ do |size|
24
30
  @retrieved_image.should be_same_image_as("test.#{size}.jpg")
25
31
  end
32
+
33
+ Then /^I should get the (.*) cropped image$/ do |size|
34
+ @retrieved_image.should be_same_image_as("test.crop.#{size}.jpg")
35
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require 'mugshot'
2
3
 
3
4
  require 'rack/test'
@@ -22,7 +23,10 @@ end
22
23
  Spec::Matchers.define :be_same_image_as do |expected_filename|
23
24
  match do |actual_blob|
24
25
  actual = Magick::Image.from_blob(actual_blob).first
25
- expected = Magick::Image.read("features/support/files/#{expected_filename}").first
26
- actual.get_pixels(0, 0, actual.columns, actual.rows) == expected.get_pixels(0, 0, expected.columns, expected.rows)
26
+ expected = Magick::Image.read(File.expand_path(__FILE__ + "/../files/#{expected_filename}")).first
27
+
28
+ actual.columns == expected.columns &&
29
+ actual.rows == expected.rows &&
30
+ actual.difference(expected)[1] < 0.01
27
31
  end
28
32
  end
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require 'rubygems'
2
3
  require 'fileutils'
3
4
  require 'uuid'
@@ -11,4 +12,4 @@ require 'mugshot/image'
11
12
  require 'mugshot/storage'
12
13
  require 'mugshot/fs_storage'
13
14
  require 'mugshot/application'
14
-
15
+ require 'mugshot/proxy'
@@ -1,4 +1,6 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require 'sinatra/base'
3
+
2
4
  class Mugshot::Application < Sinatra::Base
3
5
 
4
6
  set :static, true
@@ -6,16 +8,13 @@ class Mugshot::Application < Sinatra::Base
6
8
 
7
9
  before do
8
10
  response['Cache-Control'] = "public, max-age=#{1.year.to_i}"
9
- content_type :jpg
10
11
  end
11
-
12
+
12
13
  get '/?' do
13
- content_type :html
14
14
  'ok'
15
15
  end
16
16
 
17
17
  post '/?' do
18
- content_type :html
19
18
  @storage.write(params['file'][:tempfile].read)
20
19
  end
21
20
 
@@ -23,8 +22,21 @@ class Mugshot::Application < Sinatra::Base
23
22
  image = @storage.read(id)
24
23
  halt 404 if image.blank?
25
24
 
25
+ image.resize!(size)
26
+
26
27
  begin
27
- resize(image, size)
28
+ send_image(image, format.to_sym)
29
+ ensure
30
+ image.destroy!
31
+ end
32
+ end
33
+
34
+ get '/*/:id.:format' do |splat, id, format|
35
+ image = @storage.read(id)
36
+ halt 404 if image.blank?
37
+
38
+ begin
39
+ process_operations(image, splat)
28
40
  send_image(image, format.to_sym)
29
41
  ensure
30
42
  image.destroy!
@@ -32,12 +44,19 @@ class Mugshot::Application < Sinatra::Base
32
44
  end
33
45
 
34
46
  protected
47
+
35
48
  def initialize(storage)
36
49
  @storage = storage
37
50
  end
38
-
39
- def resize(image, size)
40
- image.resize!(size)
51
+
52
+ private
53
+
54
+ def process_operations(image, splat)
55
+ operations = Hash[*splat.split('/')]
56
+ operations.assert_valid_keys('crop', 'resize') rescue halt 404
57
+ operations.each do |op, op_params|
58
+ image.send("#{op}!", op_params)
59
+ end
41
60
  end
42
61
 
43
62
  def send_image(image, format)
@@ -1,20 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  class Mugshot::FSStorage < Mugshot::Storage
2
3
  def write(bin)
3
4
  returning asset_id do |id|
4
- File.open(File.join(@root_path, id), "w") do |fw|
5
+ File.open(File.join(@root_path, id), "w") do |fw|
5
6
  fw.write(bin)
6
7
  end
7
8
  end
8
9
  end
9
-
10
+
10
11
  def read(id)
11
12
  file = File.join(@root_path, id)
12
13
  return nil unless File.exist? file
13
14
  Mugshot::Image.new File.open(file)
14
15
  end
15
-
16
+
16
17
  protected
17
-
18
+
18
19
  def initialize(root_path)
19
20
  @root_path = root_path
20
21
  FileUtils.mkdir_p(root_path)
@@ -1,6 +1,6 @@
1
-
1
+ # -*- encoding: utf-8 -*-
2
2
  class Mugshot::Image
3
-
3
+
4
4
  def width
5
5
  @image.columns
6
6
  end
@@ -10,8 +10,8 @@ class Mugshot::Image
10
10
  end
11
11
 
12
12
  def resize!(size)
13
- w, h = size.to_s.split("x").map{|i| i.blank? ? nil : i.to_i}
14
-
13
+ w, h = parse_size(size)
14
+
15
15
  if [w, h].include?(nil)
16
16
  @image.resize_to_fit! w, h
17
17
  else
@@ -21,6 +21,12 @@ class Mugshot::Image
21
21
  self
22
22
  end
23
23
 
24
+ def crop!(size)
25
+ w, h = parse_size(size)
26
+ @image.resize_to_fill! w, h
27
+ self
28
+ end
29
+
24
30
  def destroy!
25
31
  @image.destroy!
26
32
  self
@@ -40,4 +46,9 @@ class Mugshot::Image
40
46
  @image = Magick::Image.read(file).first
41
47
  end
42
48
 
49
+ private
50
+
51
+ def parse_size(size)
52
+ size.to_s.split("x").map{|i| i.blank? ? nil : i.to_i}
53
+ end
43
54
  end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'sinatra/base'
3
+ require 'net/http'
4
+
5
+ class Mugshot::Proxy < Sinatra::Base
6
+ get '/*' do
7
+ url = URI.parse "#{@host}/#{params[:splat]}"
8
+ res = Net::HTTP.start(url.host, url.port) {|http|
9
+ http.get(url.path)
10
+ }
11
+
12
+ status res.code.to_i
13
+
14
+ headers_hash = {}
15
+ res.each_header do |k,v|
16
+ headers_hash[k] = v unless k.to_s =~ /status/i
17
+ end
18
+ headers headers_hash
19
+
20
+ res.body
21
+ end
22
+
23
+ before do
24
+ halt 405 unless request.get?
25
+ end
26
+
27
+ protected
28
+
29
+ def initialize(host)
30
+ @host = host
31
+ end
32
+ end
@@ -1,7 +1,6 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  class Mugshot::Storage
2
-
3
3
  protected
4
-
5
4
  def asset_id
6
5
  UUID.generate :compact
7
6
  end
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
3
 
3
4
  describe Mugshot::Application do
@@ -7,7 +8,7 @@ describe Mugshot::Application do
7
8
  Mugshot::Application.new(@storage)
8
9
  end
9
10
  end
10
-
11
+
11
12
  describe "POST /" do
12
13
  it "should create image" do
13
14
  file_read = nil
@@ -18,46 +19,93 @@ describe Mugshot::Application do
18
19
 
19
20
  last_response.status.should == 200
20
21
  end
21
-
22
+
22
23
  it "should return image id" do
23
24
  @storage.stub!(:write).and_return("batata")
24
25
 
25
26
  post "/", "file" => Rack::Test::UploadedFile.new("spec/files/test.jpg", "image/jpeg")
26
-
27
+
27
28
  last_response.body.should == "batata"
28
29
  end
29
30
  end
30
31
 
31
- describe "GET /:size/:id.:ext" do
32
- it "should return resized image" do
33
- image = mock(Mugshot::Image)
34
- image.stub!(:to_blob).and_return("image data")
35
- image.should_receive(:resize!).with("200x200")
36
- image.should_receive(:destroy!)
37
- @storage.stub!(:read).with("batata").and_return(image)
38
-
39
- get "/200x200/batata.jpg"
32
+ shared_examples_for 'any GET image' do
33
+ before :each do
34
+ @image = mock(Mugshot::Image, :null_object => true)
35
+ @storage.stub!(:read).with("image_id").and_return(@image)
36
+ end
40
37
 
41
- last_response.status.should == 200
38
+ it "should convert image to format" do
39
+ @image.should_receive(:to_blob).with(:format => :jpg)
40
+ perform_get
42
41
  last_response.content_type == "image/jpg"
42
+ end
43
+
44
+ it "should destroy image" do
45
+ @image.should_receive(:destroy!)
46
+
47
+ perform_get
48
+ end
49
+
50
+ it "should return image" do
51
+ @image.stub!(:to_blob).and_return("image data")
52
+
53
+ perform_get
54
+
55
+ last_response.should be_ok
43
56
  last_response.body.should == "image data"
44
57
  end
45
58
 
46
59
  it "should halt 404 when image doesn't exist" do
47
- @storage.stub!(:read).with("batata").and_return(nil)
60
+ @storage.stub!(:read).with("image_id").and_return(nil)
48
61
 
49
- get "/200x200/batata.jpg"
62
+ perform_get
50
63
 
51
- last_response.status.should == 404
64
+ last_response.should be_not_found
52
65
  last_response.body.should be_empty
53
66
  end
54
67
  end
68
+
69
+ describe "GET /:size/:id.:format" do
70
+ def perform_get
71
+ get "/200x200/image_id.jpg"
72
+ end
73
+
74
+ it_should_behave_like 'any GET image'
75
+
76
+ it "should resize image" do
77
+ @image.should_receive(:resize!).with("200x200")
78
+
79
+ perform_get
80
+ end
81
+ end
82
+
83
+ describe "GET /:ops/:ops_params/:id.:format" do
84
+ def perform_get
85
+ get "/crop/140x105/image_id.jpg"
86
+ end
87
+
88
+ it_should_behave_like 'any GET image'
89
+
90
+ it "should perform operations on image" do
91
+ @image.should_receive(:resize!).with("140x140")
92
+ @image.should_receive(:crop!).with("140x105")
93
+
94
+ get "/resize/140x140/crop/140x105/image_id.jpg"
95
+ end
96
+
97
+ it "should halt 404 on operations that are not allowed" do
98
+ @image.should_not_receive(:operation!)
99
+
100
+ get "/operation/140x105/image_id.jpg"
101
+ end
102
+ end
55
103
 
56
104
  describe "GET /" do
57
105
  it "should return ok as healthcheck" do
58
106
  get "/"
59
107
 
60
- last_response.status.should == 200
108
+ last_response.should be_ok
61
109
  last_response.body.should == "ok"
62
110
  end
63
111
  end
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
3
 
3
4
  describe Mugshot::FSStorage do
@@ -9,19 +10,19 @@ describe Mugshot::FSStorage do
9
10
  require 'fileutils'
10
11
  FileUtils.rm_rf("/tmp/mugshot/spec")
11
12
  end
12
-
13
+
13
14
  it "should write an image to the filesystem and read it back" do
14
15
  bin = File.open("spec/files/test.jpg").read
15
-
16
+
16
17
  image = Mugshot::Image.new File.open("spec/files/test.jpg")
17
18
  Mugshot::Image.stub!(:new).and_return(image)
18
19
 
19
20
  id = @fs.write(bin)
20
21
  image2 = @fs.read(id)
21
-
22
+
22
23
  image2.should == image
23
- end
24
-
24
+ end
25
+
25
26
  it "should return nil when an image with the given id doesn't exist on the filesystem" do
26
27
  @fs.read('nonexistant-id').should be_nil
27
28
  end
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
3
 
3
4
  describe Mugshot::Image do
@@ -7,7 +8,7 @@ describe Mugshot::Image do
7
8
 
8
9
  @image = Mugshot::Image.new(File.open("spec/files/test.jpg"))
9
10
  end
10
-
11
+
11
12
  it 'should return image width and height' do
12
13
  @magick_image.stub!(:columns).and_return(100)
13
14
  @magick_image.stub!(:rows).and_return(200)
@@ -56,7 +57,7 @@ describe Mugshot::Image do
56
57
  end
57
58
 
58
59
  end
59
-
60
+
60
61
  it "should resize image to given width and height" do
61
62
  @magick_image.should_receive(:resize!).with(300, 200)
62
63
  @image.resize! "300x200"
@@ -71,10 +72,15 @@ describe Mugshot::Image do
71
72
  @magick_image.should_receive(:resize_to_fit!).with(nil, 200)
72
73
  @image.resize! "x200"
73
74
  end
74
-
75
+
76
+ it "should crop image to given width and height" do
77
+ @magick_image.should_receive(:resize_to_fill!).with(140, 105)
78
+ @image.crop! "140x105"
79
+ end
80
+
75
81
  it 'should destroy image' do
76
82
  @magick_image.should_receive(:'destroy!')
77
83
  @image.destroy!
78
84
  end
79
-
85
+
80
86
  end
@@ -0,0 +1,67 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
3
+
4
+ describe Mugshot::Proxy do
5
+ before :each do
6
+ def app
7
+ Mugshot::Proxy.new("http://localhost:9292")
8
+ end
9
+ end
10
+
11
+ describe 'GET /*' do
12
+ before :each do
13
+ @http = stub(Net::HTTP, :null_object => true)
14
+ @res = stub(Net::HTTPResponse, :null_object => true)
15
+ @http.stub!(:get).with('/request/path').and_return(@res)
16
+ Net::HTTP.stub!(:start).with("localhost", 9292).and_yield(@http)
17
+ end
18
+
19
+ it 'should proxy the request to configured host' do
20
+ @res.stub!(:body).and_return('body')
21
+
22
+ get '/request/path'
23
+
24
+ last_response.body.should == 'body'
25
+ end
26
+
27
+ it 'should return original response headers' do
28
+ @res.stub!(:each_header)\
29
+ .and_yield('content-type', 'image/jpg')\
30
+ .and_yield('cache-control', 'public, max-age=31557600')
31
+
32
+ get '/request/path'
33
+
34
+ last_response.headers['content-type'].should == 'image/jpg'
35
+ last_response.headers['cache-control'].should == 'public, max-age=31557600'
36
+ end
37
+
38
+ it 'should return original status' do
39
+ @res.stub!(:code).and_return('404')
40
+
41
+ get '/request/path'
42
+
43
+ last_response.status.should == 404
44
+ end
45
+
46
+ it 'should not return status header' do
47
+ @res.stub!(:each_header).and_yield('status', '200')
48
+
49
+ get '/request/path'
50
+
51
+ last_response.headers.should_not include('status')
52
+ end
53
+ end
54
+
55
+ describe 'other methods' do
56
+ it 'should not be allowed' do
57
+ post '/request/path'
58
+ last_response.status.should == 405
59
+
60
+ put '/request/path'
61
+ last_response.status.should == 405
62
+
63
+ delete '/request/path'
64
+ last_response.status.should == 405
65
+ end
66
+ end
67
+ end
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
3
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
4
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mugshot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - "Cain\xC3\xA3 Nunes"
@@ -12,7 +12,7 @@ autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
14
 
15
- date: 2010-01-29 00:00:00 -02:00
15
+ date: 2010-02-03 00:00:00 -02:00
16
16
  default_executable:
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
@@ -103,6 +103,7 @@ files:
103
103
  - lib/mugshot/application.rb
104
104
  - lib/mugshot/fs_storage.rb
105
105
  - lib/mugshot/image.rb
106
+ - lib/mugshot/proxy.rb
106
107
  - lib/mugshot/public/crossdomain.xml
107
108
  - lib/mugshot/storage.rb
108
109
  - LICENSE
@@ -140,14 +141,17 @@ test_files:
140
141
  - spec/mugshot/application_spec.rb
141
142
  - spec/mugshot/fs_storage_spec.rb
142
143
  - spec/mugshot/image_spec.rb
144
+ - spec/mugshot/proxy_spec.rb
143
145
  - spec/spec.opts
144
146
  - spec/spec_helper.rb
145
147
  - spec/test.html
148
+ - features/crop_image.feature
146
149
  - features/retrieve_resized_image.feature
147
150
  - features/retrieve_resized_image_keeping_aspect_ratio.feature
148
151
  - features/step_definitions/all_steps.rb
149
152
  - features/support/env.rb
150
153
  - features/support/files/test.200x.jpg
151
154
  - features/support/files/test.200x200.jpg
155
+ - features/support/files/test.crop.300x200.jpg
152
156
  - features/support/files/test.jpg
153
157
  - features/support/files/test.x200.jpg