shutterbug 0.0.11 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.ruby-version +1 -1
  2. data/.travis.yml +2 -3
  3. data/Guardfile +5 -0
  4. data/README.md +59 -10
  5. data/config.ru +5 -3
  6. data/demo/{withIframe.html → iframe_example.html} +0 -0
  7. data/demo/index.html +7 -24
  8. data/demo/oil-and-water/heatbath.svg +20 -0
  9. data/demo/oil-and-water/ke-gradient.svg +27 -0
  10. data/demo/oil-and-water/oil-and-water.svg +487 -0
  11. data/demo/simple_example.html +35 -0
  12. data/demo/svg_example.html +571 -0
  13. data/images/shutterbug.jpg +0 -0
  14. data/lib/shutterbug.rb +4 -6
  15. data/lib/shutterbug/cache_manager.rb +6 -0
  16. data/lib/shutterbug/cache_manager/cache_entry.rb +19 -0
  17. data/lib/shutterbug/cache_manager/no_cache.rb +19 -0
  18. data/lib/shutterbug/configuration.rb +20 -24
  19. data/lib/shutterbug/handlers.rb +7 -0
  20. data/lib/shutterbug/handlers/convert_handler.rb +38 -0
  21. data/lib/shutterbug/handlers/file_handlers.rb +9 -0
  22. data/lib/shutterbug/handlers/file_handlers/base.rb +39 -0
  23. data/lib/shutterbug/handlers/file_handlers/html_file.rb +22 -0
  24. data/lib/shutterbug/handlers/file_handlers/png_file.rb +23 -0
  25. data/lib/shutterbug/handlers/js_file_handler.rb +27 -0
  26. data/lib/shutterbug/{shutterbug.js → handlers/shutterbug.js} +26 -3
  27. data/lib/shutterbug/phantom_job.rb +17 -12
  28. data/lib/shutterbug/rackapp.rb +33 -31
  29. data/lib/shutterbug/storage.rb +6 -0
  30. data/lib/shutterbug/storage/file_storage.rb +20 -0
  31. data/lib/shutterbug/storage/s3_storage.rb +75 -0
  32. data/shutterbug.gemspec +3 -0
  33. data/spec/shared_examples_for_file_handlers.rb +16 -0
  34. data/spec/shared_examples_for_handlers.rb +26 -0
  35. data/spec/shared_examples_for_storage.rb +17 -0
  36. data/spec/shutterbug/configuration_spec.rb +83 -0
  37. data/spec/shutterbug/convert_handler_spec.rb +29 -0
  38. data/spec/shutterbug/file_storage_spec.rb +5 -0
  39. data/spec/shutterbug/html_file_handler_spec.rb +10 -0
  40. data/spec/shutterbug/js_file_handler_spec.rb +4 -0
  41. data/spec/shutterbug/png_file_handler_spec.rb +10 -0
  42. data/spec/shutterbug/rackapp_spec.rb +49 -41
  43. data/spec/shutterbug/s3_storage_spec.rb +12 -0
  44. metadata +205 -105
  45. data/lib/shutterbug/bug_file.rb +0 -22
  46. data/lib/shutterbug/html_file.rb +0 -3
  47. data/lib/shutterbug/js_file.rb +0 -11
  48. data/lib/shutterbug/png_file.rb +0 -3
  49. data/lib/shutterbug/service.rb +0 -39
@@ -1,49 +1,51 @@
1
1
  # encoding: utf-8
2
-
3
2
  module Shutterbug
4
3
  class Rackapp
5
4
 
6
- def initialize(app, &block)
5
+ DefaultHandlers = [
6
+ Shutterbug::Handlers::ConvertHandler ,
7
+ Shutterbug::Handlers::JsFileHandler,
8
+ Shutterbug::Handlers::FileHandlers::PngFile,
9
+ Shutterbug::Handlers::FileHandlers::HtmlFile
10
+ ]
11
+
12
+ attr_accessor :handlers
13
+ def add_handler(klass)
14
+ instance = klass.new(@config)
15
+ log "adding handler for #{instance.regex} ➙ #{klass.name}"
16
+ self.handlers[instance.regex] = instance
17
+ end
18
+
19
+ def add_default_handlers
20
+ DefaultHandlers.each { |h| add_handler(h) }
21
+ end
22
+
23
+ def initialize(app=nil, &block)
24
+ @handlers = {}
7
25
  @config = Configuration.instance
8
26
  yield @config if block_given?
9
27
  @app = app
10
- @shutterbug = Service.new(@config)
28
+ add_default_handlers
11
29
  log "initialized"
12
30
  end
13
31
 
14
- def do_convert(req)
15
- html = req.POST()['content'] || ""
16
- width = req.POST()['width'] || 1000
17
- height = req.POST()['height'] || 700
18
- css = req.POST()['css'] || ""
19
-
20
- signature = @shutterbug.convert(@config.base_url(req), html, css, width, height)
21
- response_text = "<img src='#{@config.png_path(signature)}' alt='#{signature}'>"
22
- return good_response(response_text,'text/plain')
23
- end
24
-
25
32
  def call env
26
- req = Rack::Request.new(env)
27
- case req.path
28
- when @config.convert_regex
29
- do_convert(req)
30
- when @config.png_regex
31
- good_response(@shutterbug.get_png_file($1),'image/png')
32
- when @config.html_regex
33
- good_response(@shutterbug.get_html_file($1),'text/html')
34
- when @config.js_regex
35
- good_response(@shutterbug.get_shutterbug_file, 'application/javascript')
36
- else
37
- skip(env)
33
+ req = Rack::Request.new(env)
34
+ result = false
35
+ handlers.keys.each do |path_regex|
36
+ if req.path =~ path_regex
37
+ result = handlers[path_regex].handle(self, req, env)
38
+ end
38
39
  end
40
+ result || skip(env)
39
41
  end
40
42
 
41
- private
42
- def good_response(content, type, cache='no-cache')
43
+ def good_response(content, type, cachable=true)
43
44
  headers = {}
44
- headers['Content-Length'] = content.size.to_s
45
+ size = content.respond_to?(:bytesize) ? content.bytesize : content.size
46
+ headers['Content-Length'] = size.to_s
45
47
  headers['Content-Type'] = type
46
- headers['Cache-Control'] = 'no-cache'
48
+ headers['Cache-Control'] = 'no-cache' unless cachable
47
49
  # content must be enumerable.
48
50
  content = [content] if content.kind_of? String
49
51
  return [200, headers, content]
@@ -55,7 +57,7 @@ module Shutterbug
55
57
 
56
58
  def skip(env)
57
59
  # call the applicaiton default
58
- @app.call env
60
+ @app.call env if @app
59
61
  end
60
62
 
61
63
  end
@@ -0,0 +1,6 @@
1
+ module Shutterbug
2
+ module Storage
3
+ autoload :FileStorage, "shutterbug/storage/file_storage"
4
+ autoload :S3Storage, "shutterbug/storage/s3_storage"
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ module Shutterbug
2
+ module Storage
3
+ class FileStorage
4
+ attr_accessor :filename
5
+ attr_accessor :config
6
+ attr_accessor :url
7
+
8
+ def initialize(filename, file_handler)
9
+ @filename = Configuration.instance.fs_path_for(filename)
10
+ @url = file_handler.urlify(filename)
11
+ end
12
+
13
+ def get_content
14
+ file = File.open(@filename, 'r')
15
+ return file
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,75 @@
1
+ module Shutterbug
2
+ module Storage
3
+ class S3Storage
4
+ require 'fog'
5
+
6
+ attr_accessor :filename
7
+ attr_accessor :url
8
+ attr_accessor :stream_file
9
+
10
+ def self.connect!
11
+ Fog::Storage.new({
12
+ :provider => 'AWS',
13
+ :aws_access_key_id => Configuration.instance.s3_key,
14
+ :aws_secret_access_key => Configuration.instance.s3_secret
15
+ })
16
+ end
17
+
18
+ def self.connection
19
+ @connection ||= self.connect!
20
+ end
21
+
22
+ def self.create_bin
23
+ self.connection.directories.create(
24
+ :key => Configuration.instance.s3_bin,
25
+ :public => true)
26
+ end
27
+
28
+ def self.s3_bin
29
+ @s3_bin ||= self.create_bin
30
+ end
31
+
32
+ def self.write(name, filename)
33
+ full_path = Configuration.instance.fs_path_for(filename)
34
+ if self.fs_path_exists? full_path
35
+ self.s3_bin.files.create(
36
+ :key => name,
37
+ :body => File.open(full_path),
38
+ :public => true)
39
+ end
40
+ end
41
+
42
+ def self.find(path)
43
+ self.s3_bin.files.get(path)
44
+ end
45
+
46
+ def self.fs_path_exists?(long_path)
47
+ File.exists? long_path
48
+ end
49
+
50
+ def self.handler_for(type)
51
+ return self.handlers[type]
52
+ end
53
+
54
+ def initialize(long_path, filetype)
55
+ @filename = File.basename(long_path)
56
+ @source = long_path
57
+ @stream_file = S3Storage.write(@filename, long_path)
58
+ @url = @stream_file.public_url
59
+ end
60
+
61
+ def get_content
62
+ @stream_file.body
63
+ end
64
+
65
+ def size
66
+ @stream_file.content_length
67
+ end
68
+
69
+ def redirect_s3
70
+ return [301, {"Location" => self.url}, []]
71
+ end
72
+
73
+ end
74
+ end
75
+ end
data/shutterbug.gemspec CHANGED
@@ -30,7 +30,10 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency "rspec"
31
31
  spec.add_development_dependency "guard-rspec"
32
32
  spec.add_development_dependency "rack-cors"
33
+ spec.add_development_dependency "guard-rack"
34
+ spec.add_development_dependency "rack-test"
33
35
 
34
36
  spec.add_dependency "rack"
37
+ spec.add_dependency "fog"
35
38
 
36
39
  end
@@ -0,0 +1,16 @@
1
+ require 'shared_examples_for_handlers'
2
+ shared_examples "a file handler" do
3
+ let(:config) { Shutterbug::Configuration.instance}
4
+ let(:rackapp) { mock }
5
+ let(:req) { mock }
6
+ let(:env) { mock }
7
+ let(:handler) { described_class.new(config) }
8
+
9
+
10
+ it "should have a file extension" do
11
+ handler.should respond_to :file_extension
12
+ end
13
+ it "should have a mime type" do
14
+ handler.should respond_to :mime_type
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ shared_examples "a request handler" do
2
+ let(:config) { Shutterbug::Configuration.new()}
3
+ let(:rackapp) { mock }
4
+ let(:req) { mock }
5
+ let(:env) { mock }
6
+ let(:handler) { described_class.new(config) }
7
+ let(:mock_storage) do
8
+ mock({
9
+ :new => mock({
10
+ :get_content => "content"
11
+ })
12
+ })
13
+ end
14
+ before(:each) do
15
+ config.stub!(:storage => mock_storage)
16
+ end
17
+ it "should respond to regex" do
18
+ handler.should respond_to :regex
19
+ handler.regex.should be_kind_of Regexp
20
+ end
21
+ it "should respond to handle" do
22
+ handler.should respond_to :handle
23
+ rackapp.should_receive :good_response
24
+ handler.handle(rackapp,req,env)
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ shared_examples "a storage provider" do
2
+ let(:filetype) { Shutterbug::Handlers::FileHandlers::HtmlFile.new}
3
+ let(:filename) { "somefilename.html" }
4
+ let(:provider) { described_class.new(filename,filetype) }
5
+
6
+ it "should respond to filename" do
7
+ provider.should respond_to :filename
8
+ end
9
+
10
+ it "should respond to url" do
11
+ provider.should respond_to :url
12
+ end
13
+
14
+ it "should respond to get_content" do
15
+ provider.should respond_to :get_content
16
+ end
17
+ end
@@ -0,0 +1,83 @@
1
+
2
+ describe Shutterbug::Configuration do
3
+
4
+ let(:uri_prefix) { "http://blah.com/" }
5
+ let(:path_prefix) { "/shutterbug" }
6
+ let(:resource_dir) { "resource_dir"}
7
+ let(:phantom_bin_path) { "phantom_bin_path"}
8
+ let(:s3_bin) { nil }
9
+ let(:s3_key) { nil }
10
+ let(:s3_secret) { nil }
11
+
12
+ let(:opts) do
13
+ { :uri_prefix => uri_prefix,
14
+ :path_prefix => path_prefix,
15
+ :resource_dir => resource_dir,
16
+ :phantom_bin_path => phantom_bin_path,
17
+ :s3_key => s3_key,
18
+ :s3_bin => s3_bin,
19
+ :s3_secret => s3_secret
20
+ }
21
+ end
22
+
23
+
24
+ # def js_file
25
+
26
+
27
+ subject { Shutterbug::Configuration.new(opts) }
28
+
29
+ describe "#fs_path_for(filename)" do |variable|
30
+ it "should return <resource_dir>/phantom_<sha>.extension" do
31
+ subject.fs_path_for("f208004.png").should == "resource_dir/phantom_f208004.png"
32
+ end
33
+ end
34
+
35
+ describe "base_url" do
36
+ let(:post) { {} }
37
+ let(:referrer){ nil }
38
+ let(:req) { mock(:POST => post, :referrer => referrer, :scheme => "http", :host_with_port => "fake.com:80")}
39
+
40
+ describe "without explict base_url or a referrer" do
41
+ it "should use defaults" do
42
+ subject.base_url(req).should == "http://fake.com:80"
43
+ end
44
+ end
45
+ describe "with a referrer" do
46
+ let(:referrer) { "http://referrer.com/scuba.html"}
47
+ it "should use the referrer" do
48
+ subject.base_url(req).should == "http://referrer.com/scuba.html"
49
+ end
50
+ end
51
+ describe "with a POST param" do
52
+ let(:post) { {'base_url' => "http://base_url.used.com/"} }
53
+ it "should use the POST param" do
54
+ subject.base_url(req).should == "http://base_url.used.com/"
55
+ end
56
+ end
57
+ end
58
+
59
+
60
+
61
+ describe "use_s3?" do
62
+ describe "with no S3 information" do
63
+ its(:use_s3?) { should be_false }
64
+ end
65
+
66
+ describe "when s3_bin is specified" do
67
+ let(:s3_bin) { "bin" }
68
+ its(:use_s3?) { should be_false}
69
+
70
+ describe "when s3_key is specified" do
71
+ let(:s3_key) { "key" }
72
+ its(:use_s3?) { should be_false}
73
+
74
+ describe "when s3_secret is specig" do
75
+ let(:s3_secret) { "secret" }
76
+ its(:use_s3?) { should be_true}
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+
83
+ end
@@ -0,0 +1,29 @@
1
+ require 'shared_examples_for_handlers'
2
+ describe Shutterbug::Handlers::ConvertHandler do
3
+ let(:mock_post) do
4
+ mock(:POST => {},
5
+ :referrer => nil,
6
+ :scheme => nil,
7
+ :host_with_port => nil)
8
+ end
9
+ let(:req) { mock_post }
10
+
11
+ it_behaves_like "a request handler" do
12
+ let(:req) { mock_post }
13
+ end
14
+
15
+ describe "calling phantom" do
16
+ let(:mock_results) { mock }
17
+ let(:rackapp) { mock(:good_response => true )}
18
+ let(:env) { mock }
19
+ let(:fake_file) { mock(:filename => "blub", :url => "glub")}
20
+ let(:mock_fantom) { mock(:cache_key => "1", :html_file => fake_file, :png_file => fake_file) }
21
+ before(:each) do
22
+ Shutterbug::PhantomJob.stub!(:new => mock_fantom)
23
+ end
24
+ it "should invoke phantom" do
25
+ mock_fantom.should_receive(:rasterize).and_return(mock_results)
26
+ subject.handle(rackapp,req,env)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ require 'shared_examples_for_storage'
2
+ describe Shutterbug::Storage::FileStorage do
3
+ it_behaves_like "a storage provider" do
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require 'shared_examples_for_file_handlers'
2
+ require 'shared_examples_for_handlers'
3
+ describe Shutterbug::Handlers::FileHandlers::HtmlFile do
4
+ it_behaves_like "a file handler" do
5
+ let(:req) { mock(:path => "/shutterbug/get_html/foobar.html") }
6
+ end
7
+ it_behaves_like "a request handler" do
8
+ let(:req) { mock(:path => "/shutterbug/get_html/foobar.html") }
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ require 'shared_examples_for_handlers'
2
+ describe Shutterbug::Handlers::JsFileHandler do
3
+ it_behaves_like "a request handler"
4
+ end
@@ -0,0 +1,10 @@
1
+ require 'shared_examples_for_file_handlers'
2
+ require 'shared_examples_for_handlers'
3
+ describe Shutterbug::Handlers::FileHandlers::PngFile do
4
+ it_behaves_like "a file handler" do
5
+ let(:req) { mock(:path => "/shutterbug/get_png/foobar.png") }
6
+ end
7
+ it_behaves_like "a request handler" do
8
+ let(:req) { mock(:path => "/shutterbug/get_png/foobar.png") }
9
+ end
10
+ end
@@ -1,17 +1,22 @@
1
1
  require 'rack'
2
+ require 'rack/test'
3
+
2
4
  RSpec::Matchers.define :be_happy_response do |filetype|
3
5
  match do |actual|
4
6
  actual[0] == 200 || actual[3] == filetype
5
7
  end
6
8
  end
7
9
 
10
+ RSpec::Matchers.define :be_redirect_response do |url|
11
+ match do |actual|
12
+ actual[0] == 200 || actual[1]["Location"] == url
13
+ end
14
+ end
15
+
8
16
  describe Shutterbug::Rackapp do
9
- let(:sha) { "542112e" }
10
- let(:size) { 200 }
11
- let(:rackfile){ mock :fackfile, :size => size }
12
- let(:service) { mock :service }
13
- let(:app) { mock :app }
14
- let(:config) { Shutterbug::Configuration.instance }
17
+ include Rack::Test::Methods
18
+
19
+ let(:config) { Shutterbug::Configuration.instance }
15
20
 
16
21
  let(:post_data) do
17
22
  {
@@ -23,61 +28,64 @@ describe Shutterbug::Rackapp do
23
28
  }
24
29
  end
25
30
 
26
- subject { Shutterbug::Rackapp.new(app) }
31
+ let(:app) do
32
+ Shutterbug::Rackapp.new do |config|
33
+ config.uri_prefix = "http://localhost:9292"
34
+ config.path_prefix = "/shutterbug"
35
+ end
36
+ end
27
37
 
28
- before(:each) do
29
- Shutterbug::Service.stub!(:new => service)
38
+ let(:filename) { "filename" }
39
+ let(:url) { "url_to_file" }
40
+
41
+ let(:mock_file) do
42
+ mock({
43
+ :get_content => "content",
44
+ :filename => filename,
45
+ :url => url
46
+ })
30
47
  end
31
48
 
32
- describe "#do_convert" do
33
- let(:req) { mock :req, :POST => post_data}
34
- it "should return a valid image url" do
35
- service.should_receive(:convert).and_return(sha)
36
- (resp_code,headers,content) = subject.do_convert(req)
37
- resp_code.should == 200
38
- headers['Content-Type'].should == 'text/plain'
39
- content[0].should match /^<img src='[^']+'[^>]+>$/
40
- headers['Content-Length'].should == content[0].size.to_s
41
- end
49
+ let(:test_storage) { mock({ :new => mock_file })}
50
+
51
+ before(:each) do
52
+ config.stub!(:storage => test_storage)
42
53
  end
43
54
 
44
- describe "rounting requests in #call" do
45
- let(:path) { 'bogus' }
46
- let(:req) { mock(:req, :path => path) }
47
- before(:each) do
48
- Rack::Request.stub!(:new).and_return(req)
49
- end
55
+ describe "routing requests in #call" do
50
56
 
51
- describe "convert route" do
52
- let(:path) { config.convert_path }
53
- let(:image_response) { mock :image_response }
54
- it "should route #do_convert" do
55
- subject.should_receive(:do_convert, :with => req).and_return image_response
56
- subject.call(mock).should == image_response
57
+ describe "do_convert route" do
58
+ it "should return a valid image url" do
59
+ get "/shutterbug/make_snapshot/", post_data
60
+ last_response.should be_ok
61
+ last_response.headers['Content-Type'].should match 'text/plain'
62
+ last_response.body.should match(/^<img src='url_to_file'[^>]+>$/)
57
63
  end
58
64
  end
59
65
 
60
66
  describe "get png route" do
61
- let(:path) { config.png_path(sha) }
62
67
  it "should route #do_get_png" do
63
- service.should_receive(:get_png_file, :with => sha).and_return rackfile
64
- subject.call(mock).should be_happy_response('image/png')
68
+ Shutterbug::Configuration.instance.stub!(:storage => test_storage)
69
+ get "/shutterbug/get_png/filename.png"
70
+ last_response.should be_ok
71
+ last_response.headers['Content-Type'].should match 'image/png'
65
72
  end
66
73
  end
67
74
 
68
75
  describe "get html route" do
69
- let(:path) { config.html_path(sha) }
70
76
  it "should route #do_get_html" do
71
- service.should_receive(:get_html_file, :with => sha).and_return rackfile
72
- subject.call(mock).should be_happy_response('text/html')
77
+
78
+ get "/shutterbug/get_html/filename.html"
79
+ last_response.should be_ok
80
+ last_response.headers['Content-Type'].should match 'text/html'
73
81
  end
74
82
  end
75
83
 
76
- describe "get shutterbug javascipt route" do
77
- let(:path) { config.js_path }
84
+ describe "get shutterbug.js javascipt route" do
78
85
  it "should route #do_get_shutterbug" do
79
- service.should_receive(:get_shutterbug_file).and_return rackfile
80
- subject.call(mock).should be_happy_response('application/javascript')
86
+ get "/shutterbug/shutterbug.js"
87
+ last_response.should be_ok
88
+ last_response.headers['Content-Type'].should match 'application/javascript'
81
89
  end
82
90
  end
83
91