shutterbug 0.0.11 → 0.1.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.
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